Compare commits

..

117 Commits

Author SHA1 Message Date
NickVs2015 aa42cd495f chore: bump version 2026-03-12 09:57:23 +03:00
vkamn 477afb9d85 chore: bump version (#2336) 2026-03-10 22:22:37 +08:00
NickVs2015 f969fcdbb8 fix: restore dpad functionality ATV (#2335) 2026-03-10 22:19:55 +08:00
vkamn b0ca16d861 chore: bump version (#2331) 2026-03-09 18:29:56 +08:00
NickVs2015 9963359948 fix: disable gamepad for GP (#2321) 2026-03-09 17:39:50 +08:00
vkamn ca639d293d chore: bump version (#2319) 2026-03-06 23:11:03 +08:00
NickVs2015 83d045af64 fix: GP requrements (#2312) 2026-03-06 17:05:16 +08:00
NickVs2015 aea8ff4961 fix: add handle handleContextCreationFailure (#2309) 2026-03-03 22:04:45 +08:00
vkamn 1892db4375 fix: remove nested qeventloop from isConfigValid (also rename to validateConfig) (#2305)
* fix: remove nested qeventloop from isConfigValid (also rename to validateConfig)

* chore: bump version
2026-03-03 20:58:32 +08:00
NickVs2015 c86a641e05 fix: add suppord android 9 gamepad and remote control (#2302) 2026-03-03 15:14:51 +08:00
vkamn befb2bf19a chore: bump version (#2295) 2026-02-27 23:33:37 +08:00
vkamn 7ad6bc340c chore: add translations for ru (#2285)
* chore: add translations for ru

* chore: text fixes
2026-02-27 20:00:31 +08:00
vkamn 9164e38c34 fix: restore backup android (#2291)
* fix: fixed restore backup on android

* chore: add resume helper for android

* chore: add ResumeHelper.runWhenActive call after all native android dialogs

* fix: add permission for tv file picker

* fix: add file picker handler in kotlin

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2026-02-27 18:43:36 +08:00
vkamn 8f7559f01b chore: revert PR #2222 (#2290) 2026-02-27 14:29:25 +08:00
vkamn af56200735 fix: fixed adding s3 s4 when updating the server conf for awg lagacy (#2289) 2026-02-27 14:11:40 +08:00
vkamn 3874050fae fix: again fixed s3, s4 ranges (#2288) 2026-02-27 13:37:49 +08:00
vkamn 3087163e34 fix: fixed s3, s4 ranges (#2283) 2026-02-26 22:31:41 +08:00
Mitternacht822 1fa152845c fix: generate native awg config as qr series (#2221) 2026-02-26 22:31:18 +08:00
vkamn 50e23ef233 fix: awg config update (#2281)
* fix: fixed client config update for awg container

* chore: bump version
2026-02-26 22:12:58 +08:00
Yaroslav Gurov ea648466de chore: remove redundant VpnConnection usage from SitesController (#2278) 2026-02-26 17:55:08 +08:00
Yaroslav Gurov b782775016 fix: change event looping to mutexes for settings and secureqsettings (#2270) 2026-02-26 11:41:08 +08:00
NickVs2015 89a7fe1081 fix: fixed remote control for ATV (#2277) 2026-02-26 11:40:16 +08:00
Yaroslav Gurov e8bb096025 fix: ios wrong awg blob (#2272) 2026-02-24 17:56:17 +07:00
Mitternacht822 fd5c7c8322 fix: copy LICENSE to build as LICENSE.txt for WiX CPack (#2265)
* fix(installer): copy LICENSE to build as LICENSE.txt for WiX CPack

* fix: fixed a typo

* fix: fixed a typo
2026-02-24 14:07:48 +08:00
Yaroslav Gurov e798d0f503 feat: update amneziawg-android dependencies (#2269) 2026-02-24 00:54:55 +08:00
Yaroslav Gurov bbb0abb596 feat: update xray (#2267) 2026-02-24 00:27:29 +08:00
vkamn 0925aec86a chore: bump version (#2264) 2026-02-23 18:01:59 +08:00
Yaroslav Gurov b084c4c284 fix: ios connection status stuck (#2263) 2026-02-23 18:00:13 +08:00
vkamn 87288ebccd chore: bump version (#2262) 2026-02-23 17:16:24 +08:00
vkamn fcd7eadf4c chore: bump version (#2261) 2026-02-23 15:38:27 +08:00
Mitternacht822 0373338fb7 fix: randomized baseUrls traversal order in GatewayController::getProxyUrls (#2247) 2026-02-23 15:33:35 +08:00
Yaroslav Gurov 42f070fe9d fix: handle Android disconnected status properly (#2255) 2026-02-23 15:31:15 +08:00
Mitternacht822 02be6dc5f9 chore: add license to msi installer (#2227) 2026-02-20 12:13:08 +08:00
vkamn bfcf7f0305 chore: bump version (#2244) 2026-02-19 20:27:42 +08:00
Mitternacht822 2bce595ade fix: remove revoke from remove subscription flow (#2226)
* fix(revoke): now revoke calls only for unlink device action

* fix: removed revoke call when removing a subscription from the app
2026-02-19 20:23:13 +08:00
Yaroslav Gurov cd1e561fd4 fix: add network watcher back (#2240)
* feat: add reconnect in case of changing network

* fix: reconnect to VPN on wakeup

* fix: linux wakeup build
2026-02-19 20:21:49 +08:00
Mitternacht822 9bd1e6a0f5 fix: added stop and delete AmneziaVPNSplitTunnel on uninstall (#2222) 2026-02-18 11:21:59 +08:00
Yaroslav Gurov 5058c9aa6f fix: do not enable killswitch by default when service starts (#2232) 2026-02-18 10:59:16 +08:00
vkamn d78416835c chore: change default i1 value (#2216) 2026-02-13 17:10:10 +08:00
vkamn 40e6c6aae3 feat: native wg with obfuscation (#2209)
* chore: change default i1 value

* feat: add i1 to native wg with obfuscation
2026-02-12 11:34:52 +08:00
Yaroslav Gurov 911a999c64 fix: xray stability and split-tunneling (#2187)
* fix: xray heap corruption

* fix: use proper configuration for split-tunneled apps

* chore: enable killswitch

* chore: xray windows split-tunneling cleanup

* chore: proper xray killswitch log

* feat: add wait for the tun device

* chore: update amnezia_xray deps for macos

* fix: add nullptr check for split-tunnel on win

* fix: modernize vpnAdapter grabbing function

* fix: remove network watcher due to its fragileness

* chore: xrayprotocol cleanup

* fix: correct wrong iface index on win

* chore: move tun2socks implementation to the client from the service

* chore: xrayprotocol cleanup

* chore: more xrayprotocol cleanup

* fix: consistent tun device with GUID specified

* chore: tun2socks logs

* chore: PrivilegedProcess cleanup
* better error handling in establishment phase
* terminate&kill ops for remote process

* fix: straighforward killing the process on windows

* fix: finally remove GUID setting from tun2socks due to instability

* fix: add sanitizer to ipc process

* chore: do not collect sensitive info from tun2socks
2026-02-11 23:47:28 +08:00
MrMirDan b4f4184aa6 fix: returned mentioned lines (#2205) 2026-02-11 23:44:11 +08:00
NickVs2015 5c6db4b7a4 fix OpenGl error (#2185) 2026-02-10 11:15:31 +08:00
vkamn f6277cdbb2 fix: native wg obfuscation (#2199)
* chore: bump version

* fix: fixed native wg obfuscation
2026-02-09 10:54:30 +08:00
NickVs2015 99312e61d3 fix: allow start Gamepad only Android (#2198) 2026-02-09 10:40:48 +08:00
NickVs2015 9f0ae75a2f feat: add gamepad buttons support android (#2066)
* feat: add support gamepad buttons

* feat: add support gamepad with github repo

* feat: add gitmodules dependency

* feat: add submodule qtgamepad

* chore: update qtgamepad submodule to commit 4e57142e563b931766056b4c7507c16892260222

* fix: update qtgamepad with standard CMake and private headers support

Update qtgamepad to commit f72b3e0 which:
- Replaces qt_add_library with standard add_library to avoid Qt 6.10 macro conflicts
- Copies private headers to build include tree for Android backend
- Creates Qt:: and Qt6:: namespace aliases for proper linking
2026-02-05 22:57:15 +08:00
vkamn 7960d8015d feat: add EULA and policy on IAP page (#2189) 2026-02-05 20:23:06 +08:00
vkamn 5dcc64e5e5 fix: deploy qopensslbackend on windows (#2190) 2026-02-05 20:22:47 +08:00
MrMirDan 964436ad43 fix: placeholder color, hide button image transparency, removed some lines (#2123)
* fix: placeholder color, hide button image transparency, removed unneccessary lines

* update: removed opacity on tunneling page

* update: remove opacity on app tunneling page
2026-02-05 12:56:41 +08:00
ik 4fc3900fd5 Merge pull request #2184 from amnezia-vpn/chore/add-release-date-upload
chore: add sending of release_date to s3
2026-02-04 12:20:23 +03:00
irvinklause 8f5e42dd61 chore: add sending of release_date to s3 2026-02-04 07:38:44 +00:00
Yaroslav Gurov 24895752c1 fix: added enablePeerTraffic call to xray (#2179)
* fix: add enablePeerTraffic call to xray

* chore: remove unnecessary steps during xray TUN setup phase

* chore: move tun init from tun2socks code to ipcserver

* chore: rework xray routing
* get rid of redundant delays
* check if remote calls are successful

* chore: xray routing fine-tuning

* fix: add service qt deps to deployment build
2026-02-04 12:35:53 +08:00
vkamn 87eccfb4ca fix: fix scrolling on drawers (#2183) 2026-02-04 12:35:17 +08:00
ik a983d0504e fix: add checks for script components to find out where it can fall (#2169) 2026-01-30 14:43:30 +08:00
vkamn d0b8535395 fix: update tag deploy (#2168) 2026-01-30 13:15:50 +08:00
dpamnezia f84480cf56 chore: fix artifacts upload (#1961) 2026-01-30 12:43:21 +08:00
MrMirDan de7a026ec1 fix: change drawer parents interactivity (#2004)
* fix: change drawer parents interactivity

* update: better vars names
2026-01-30 12:42:53 +08:00
MrMirDan a128c7d247 fix: keyboard navigation (#2023)
* fix: self-hosted easy install card

* fix: label double click when enter/return pressed
2026-01-30 12:42:29 +08:00
MrMirDan f316f0e25a feat: news notifications switch (#2126)
* feat: news notifications switch

* update: text changes

* fix: notifications enabled by default
2026-01-30 12:19:50 +08:00
NickVs2015 ea5242e29b fix: fixed cipher selection (#2110) 2026-01-30 12:18:54 +08:00
NickVs2015 b31a62c55f feat: add support open files by atv (#2082) 2026-01-30 12:11:26 +08:00
yyy-amnezia 02e3107a23 feat: implement service kickstart and improve macos post install script (#2131) 2026-01-30 12:05:20 +08:00
lunardunno 1862850108 feat: checking linux kernel version when installing amneziawg-go (#2098)
* Checking Linux kernel version when installing amneziawg-go

print the Linux kernel version to stdOut for subsequent checking by the server controller.

* Add error for old linux kernel

Add error 214 ServerLinuxKernelTooOld

* Add case for old linux kernel

Add case for error 214 ServerLinuxKernelTooOld

* Added kernel check for Awg2

Added Linux kernel version check and introduced corresponding ServerLinuxKernelTooOld error for Awg2.
2026-01-30 12:04:27 +08:00
vkamn f73792844c chore: revoke #2148 (#2160) 2026-01-26 19:39:47 +08:00
Yaroslav Gurov a7199ca6f5 fix: add +x permissions to wireguard-go on linux (#2159) 2026-01-26 19:16:39 +08:00
vkamn 5e757cdd3b chore: bump qt version for linux build (#2157) 2026-01-25 21:35:16 +08:00
vkamn 92af1f3268 chore: runners (#2150)
* chore: change runner for linux and android

* chore: add libsecret to linux build

* chore: bump version
2026-01-23 12:05:31 +08:00
Yaroslav Gurov aad9d6dae2 chore: remove redundant gateway (#2148) 2026-01-22 18:21:15 +08:00
Yaroslav Gurov 423fe3fd4f fix: remove redundant gateway from xrayprotocol (#2147) 2026-01-22 18:03:36 +08:00
vkamn b591dd7445 fix: minor fixes (#2137)
* refactor: removed premv1 migration code

* fix: i1-i5 parsing when scaning server

* chore: bump version
2026-01-19 14:03:54 +08:00
vkamn a45bb5ea4f chore: bump version (#2108)
* chore: bump version

* chore: fix deploy.yml

* chore: return jurplel/install-qt-action@v3

* chore: bump qt version

* chore: disable cache

* chore: fix qt bin folder path

* chore: downgraded qt version for linux

* chore: disable gradle cache

* chore: use large runner for linux and android

* chore: change runner name for android and linux

* fix: change github runner label

* fix: set github runner specific os version in label

* chore: add self-hosted runner ubuntu-24.04-4cores

* fix: changed label to self-hosted for github runners

* fix: changed label to 4-core for github runners

* fix: fixed app closing delay

* fix: fixed awg description

* chore: bump version

---------

Co-authored-by: irvinklause <ik@amnezia.org>
2026-01-15 15:48:48 +08:00
yyy-amnezia d859b111ca feat: awg connection states (#2091)
* Submodule amneziawg-apple updated

* feat: add support for controlled junk and special handshake timeout in AWG configurator

* refactor: improve AWG configurator and iOS controller logic

* awg_configurator.cpp reverted
2025-12-30 10:45:32 +08:00
Artyom Titov 52031efc48 fix(): set desktopFileName for Wayland (#2104) 2025-12-29 19:18:44 +08:00
vkamn d78202c612 chore: is-test-flight processing (#2093)
* fix: context menu fixes for qt6.9

* chore: is-test-flight porcessing

* chore: bump version and minor build fixes

* refactor: moved test purchase processing on client side

* fix: fixed free import on ios

* chore: bump qt version in deploy.yml

* fix: minor fixes
2025-12-29 19:18:03 +08:00
yyy-amnezia 6bac948633 refactor: move iOS/macOS NE specific disconnect logic to the top of disconnectFromVpn method (#2100) 2025-12-27 11:09:11 +08:00
vkamn a4c4ef71fb fix: minor fixes (#2099)
* fix: fixed saving i1-i5 fields

* fix: fixed default value for s4

* fix: fixed server name when sharing admin config
2025-12-26 22:55:57 +08:00
Yaroslav Gurov 127f85f4f0 fix: replace arm64 macos awg blob with amd64 one (#2096) 2025-12-24 22:28:31 +08:00
MrMirDan 13d4ddd292 chore: ru translation (#2086) 2025-12-23 20:17:27 +08:00
lunardunno 7265e09c85 chore: improved retrieving of images list (#2084)
Improved retrieving list of images named amnezia for Docker Engine 29.1.3 cleanup.
2025-12-23 12:20:44 +08:00
Yaroslav Gurov 2e629b6dac chore: bump awg version (#2088) 2025-12-19 23:40:48 +08:00
Yaroslav Gurov 92aba49705 fix: cannot connect to IPC on Windows (#2083)
* fix: replace localsocket by QtRO-embedded one

* fix: make IpcClient initialization lazy
2025-12-19 22:44:42 +08:00
vkamn bec06b3a5e chore: bump version (#2080) 2025-12-19 11:46:10 +08:00
Yaroslav Gurov 91cd9474ea fix: safe IpcClient calls (#2076)
* fix: safe IpcClient calls

* fix: double free by specifying parent

* fix: windows includes for ikev2
2025-12-19 11:09:50 +08:00
Yaroslav 6178b05643 feat: ios in-app purchase methods (#1652)
* Add in-app purchase methods

* fix: init StoreKit controller on startup

* fix: Add transaction details to StoreKit callbacks

* nullpointer access fixed

* feat: in app purchase for ios

* feat: add IAP product fetching and logging for iOS platform

* feat: iOS Simulator building pipeline made

* feat: add support for multiple IAP product IDs and attempt purchase of the first valid one

* feat: add support for retrieving Base64-encoded app receipt after successful IAP purchase

* refactor: inapp-purchase code cleanup

* feat: iap processing

* refactor: move to storekit 2

* feat: add request to billing

* chore: add ios ifdef

* feat: remove iOS simulator specific code and exclusions

* refactor: remove unused StoreKit 2 transaction observer and simplify IAP product fetching logic

* feat: implement StoreKit 2 for iOS and macOS, add restore purchases functionality

* fix: Restore Purchases button appearance updated

* feat: enhance error handling and duplicate config detection in ApiConfigsController

* feat: add support for Mac OS NE in-app purchases and StoreKitController

* ci-cd fix

* Revert "ci-cd fix"

This reverts commit f22fd7a13b.

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
Co-authored-by: vkamn <vk@amnezia.org>
Co-authored-by: spectrum <yyy@amnezia.org>
2025-12-18 22:36:12 +08:00
vkamn 46ce22b85c fix: fixed awg2 container processing (#2067) 2025-12-18 22:25:20 +08:00
NickVs2015 36edafb985 feat: add qt 6.10.1 support (#2065)
* feat: switch to qt 6.10.1

* feat: switch to qt 6.10.1 remove touch
2025-12-18 20:18:32 +08:00
Yaroslav Gurov d77eaba500 fix: make ipc client thread-safe (#2075) 2025-12-18 20:18:11 +08:00
yyy-amnezia 6a3d43fbb0 fix: iPad startup crash fix (#2071) 2025-12-17 21:54:27 +08:00
yyy-amnezia 4975955bbe feat: update GitHub workflow to use latest macOS, Xcode, and Qt versions, and add Go installation and gomobile setup (#2073) 2025-12-17 21:53:12 +08:00
Yaroslav Gurov 8f508783e3 fix: make ipc connection a singleton (#2069) 2025-12-16 23:05:31 +08:00
NickVs2015 f50817c43c feat: switch to qt 6.10.1 (#2057)
* feat: switch to qt 6.10.1

* feat: switch to qt 6.10.1 remove touch
2025-12-15 21:56:36 +08:00
Yaroslav Gurov 54f67b3d82 feat: native split-tunneling for xray (#1899)
* feat: integrated xray as a library and added split-tunneling

* fix: added copying amnezia_xray.dll to build dir

* fix: changed path on darwin

* chore: clean up getting default device

* chore: removed WSAGetLastError from sockopt logging

* fix: get rid of debug logs in xray handlers

* fix: minor fixes and xray debugging capabilities

* fix: macos default interface fix

* fix: roll-back ipv6 sockopt for mac

* fix: bind IPv6 on Windows

* fix: (win) better IPv6 handling and router fixes

* feat: prebuilts uploaded

* fix: removed redundant cmake definitions

* feat: moved xray to service process, reworked errors

* fix: return values in networkUtilities

* fix: macos build fixes

* fix: (windows) cmake fixes

* fix: (windows) compilation fix

* fix: (windows) changed location of amnezia_xray.dll

* feat: xray logs added to system service

* chore: bump xray&tun2socks versions for android

* chore: cleanup of XrayProtocol class
* removed killswitch
* removed redundant members and basic cleanup

* feat: support split-tunneling in iOS and macOS NE

* chore: update active interface index based on network path and available interfaces

* refactor: update network path handling and logging in PacketTunnelProvider

* chore: bump xray deps

---------

Co-authored-by: Yaroslav Yashin <yaroslav.yashin@gmail.com>
2025-12-15 21:54:34 +08:00
vkamn d669adb707 feat: msi installer and cli command (#2020)
* feat: Add msi quite installer

* chore: update code for new wix

* feat: add cpack wix installer

* feat: add gihub workflow for msi

* chore: fix deploy script

* chore: add wix logs

* chore: fix msi build

* chore: fix msi build

* chore: add wix exts log

* chore: add cpackwixpatch for registering the service

* chore: fix build script

* chore: fix wix fragment

* feat: add closing app with reinstalling

* chore: update version for test

* chore: fix build script

* feat: added cli commands --connect and --import (#1967)

* fix: delete unused file and disable rollback after unsuccessful service start in msi installer

* fix: Add deps to msi

* fix: msi deps

* feat: added os signal handler

* fix: incorrect import at the empty client start (#2024)

* chore: add force quit for os signal handler

* feat: os signal handler improvements

* fix: fixed --connection command

---------

Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
Co-authored-by: aiamnezia <ai@amnezia.org>
Co-authored-by: Mitternacht822 <sb@amnezia.org>
2025-12-11 18:54:24 +08:00
albexk 5103bc640e feat: implement reconnection in AWG by turning the VPN off and on (#2046) 2025-12-11 18:51:19 +08:00
vkamn 3e6f0c0342 feat: add timestamp to news list page (#2050) 2025-12-11 18:51:01 +08:00
vkamn 40950b92ee feat: awg 2 support (#1836)
* Add updated awg container

* add missing files

* Hide uninstalled AwgLegacy container

* Fix resources file

* Add role for allowed for installation containers

* Add native config sharing for new Awg container

* Fix not opening awg settings

* Remove AwgLegacy from wizard manual installation page

* Fix AmneziaWG settings

* chore: update link to submodule

* refactor: remove j1-j3 and itime

* chore: return s3 s4 fields to ui

* fix: awg2 native config compatability

* chore: update packet size validation

* feat: add awg2 support in self-hosted containers

* fix: delete parameters from server config

* feat: add H-parameters  validation as a strings

* chore: update link to submodule

* chore: add containers type for awg 1.5 and awg 2

* chore: fixed s3/s4 visibility for awg 1

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
2025-12-11 15:18:36 +08:00
AnhTVc ac77b4ee75 feat: add network status check for awg/wg protocol (#1894)
* Add network  status check for AWG/WG protocol

* Use service for PingSender

* Cleanup unused code

* Use networkchecker for all protocols

* fix android build

* add delay for ping checker stop

* handle for interafe problems on windows

* Restart IpcClient after OS suspend

* Add DBus network checker for Linux

* Use ping check for tun interfce

* Windows suspend mode handler

* MacOS suspend mode handler draft

* Add delay for Linux wakeup reconnect

* Add delay for Linux wakeup reconnect

* Fix macOS  wakeup/sleep prob

Fix macOS not receiving wakeup/sleep events

* fix done

* Update deploy.yml

fix CICD

* Update vpnconnection.cpp

update fix build CICD

* Update vpnconnection.cpp

update fix build cicd macos

* Update deploy.yml

fix  CICD build macos

* Update deploy.yml

fix CICD macos

* feat: implement SCP write buffer, improve network check and refactor macOS OpenGL support

* feat: add tunnel addresses updated signal and handle network check based on gateway and local address availability

* refactor: improve IpcClient connection handling and instance management

* fix: scp revert.

* fix: cmake reverted.

* fix: submodules updated

---------

Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
Co-authored-by: Yaroslav Yashin <yaroslav.yashin@gmail.com>
Co-authored-by: vkamn <vk@amnezia.org>
2025-12-02 12:46:24 +08:00
NickVs2015 fbf652f818 feat: add vless string on sharing screen (#1999)
* feat: add vless config string and serialization

* feat: add vless config string and serialization
2025-12-02 12:09:04 +08:00
vkamn bbbf4891e6 fix: fixed define name for linux os signal handler (#2030) 2025-12-02 11:14:09 +08:00
MrMirDan 20d005d66c fix: clear file name to remove header (#1984)
* fix: clear file name to remove header

* update: clear on signal

* removed uneccessary function

* fix: clear filename on invalid config type

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-12-02 11:13:26 +08:00
MrMirDan c81ae2b060 fix: update or delete news on newsModel update (#2007)
* fix: update or delete news on newsModel update

* update: changed check for news editing

* update: changed news edit updating

* update: changed news model updating method

* chore: add rich text support for news page

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-12-01 20:23:14 +08:00
Yaroslav 105c42db1c fix: ipc call in macos ne (#1986) 2025-12-01 10:54:42 +08:00
Mykola Baibuz 89818ff63d fix: app freeze on quit (#1804)
* fix: app freeze on quit

* fix: typo in VpnConnection destructor

* add trace info

* add more trace info

* set timelimit for flushDns

* Refactor IpcClient::Interface access logic

* cleanup unused variable

* cleanup trace info

* fix: remove second disconnect from VPN on app close

* this object will be deleted at app close

* Don't terminate VPN thread on Linux

* Revert "Don't terminate VPN thread on Linux"

This reverts commit 20e4ea2d4a.

* disconnect all signals from vpnconnection on exit

* add interruption request on vpnConnectionThread

* use checktimer only for iOS

* disconnect all signals from vpnconnection on exit

* disconnect signals on exit before VPN disconnect

* add disconnectSlots method

* fix: add allow traffic rules on killswitch disable

* wait for response from service before object destroy

* change disconnect from vpn order

* add delay for connection close

* change disconnect method order

* use stop method for protocol disconnecect

* change disconnect method order

* allow dns traffic after app close

* delete tun on disconnect

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-12-01 10:49:16 +08:00
vkamn 414c422177 feat: added os signal handler (#2029) 2025-12-01 10:45:06 +08:00
NickVs2015 b39ac8556c feat: add right artifact name (#2018) 2025-11-28 12:08:38 +08:00
MrMirDan 5e1742262d fix: eye icon (#1985)
Co-authored-by: vkamn <vk@amnezia.org>
2025-11-28 11:00:53 +08:00
VoyNaLunu 5a07a1274f fix: GetBestRoute always returning 1231 error (#1981)
* fix GetBestRoute always returning 1231 error

* revert some changes because fix turned out to be simpler
2025-11-26 12:46:55 +08:00
MrMirDan 7b8ff1fd6e fix: checked format after changing protocol (#1937)
* fix: checked format after changing protocol

* update: improved some lines

* fix(ui): restore checkmark for connection format after switching protocol

* fix: correct a typo

* fix(ui): escape regex in client search filter

* refactor: removed redundant lines

---------

Co-authored-by: Mitternacht822 <sb@amnezia.org>
2025-11-26 12:07:24 +08:00
MrMirDan c7221832e0 fix: users search field clears on 'x' button or 'escape' key clicked (#1920) 2025-11-26 11:57:28 +08:00
NickVs2015 eb7d031c7d fix: clear qt cache on start app (#2008)
* Fix/ Cache clear Android

* Fix: Clear cache on start app

* chore: bump version

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-11-26 11:47:50 +08:00
vkamn 3b3a0aaceb chore: bump version (#1997) 2025-11-18 00:22:58 +08:00
vkamn 01ec79b7d5 fix: news fetch (#1994)
* fix: fixed news nested qml call

* feat: async proxy bypass
2025-11-18 00:21:02 +08:00
vkamn 3d6339e2dd chore: bump version (#1989) 2025-11-14 13:59:47 +08:00
NickVs2015 b4d78d865a fix: fix android crash (#1988) 2025-11-14 13:57:52 +08:00
NickVs2015 b53cdcff08 fix: fix self-hosted TextFields and Keyboard reset issue (#1983)
Co-authored-by: vkamn <vk@amnezia.org>
2025-11-12 15:57:53 +08:00
vkamn 3cc18c5807 chore: bump version (#1982) 2025-11-11 23:03:24 +08:00
NickVs2015 5fdce1e49e fix: fix ui android issues (#1980)
* Fix UI issues

* Fix Screen Swipe
2025-11-11 22:03:27 +08:00
236 changed files with 6946 additions and 3403 deletions
+141 -54
View File
@@ -10,10 +10,10 @@ env:
jobs: jobs:
Build-Linux-Ubuntu: Build-Linux-Ubuntu:
runs-on: ubuntu-22.04 runs-on: android-runner
env: env:
QT_VERSION: 6.6.2 QT_VERSION: 6.10.1
QIF_VERSION: 4.7 QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -30,13 +30,15 @@ jobs:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
host: 'linux' host: 'linux'
target: 'desktop' target: 'desktop'
arch: 'gcc_64' arch: 'linux_gcc_64'
modules: 'qtremoteobjects qt5compat qtshadertools' modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true' setup-python: 'true'
tools: 'tools_ifw' tools: 'tools_ifw'
set-env: 'true' set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}' aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Get sources' - name: 'Get sources'
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -44,24 +46,31 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' - name: 'Get version from CMakeLists.txt'
uses: hendrikmuhs/ccache-action@v1.2 id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project' - name: 'Build project'
run: | run: |
sudo apt-get install libxkbcommon-x11-0 sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh bash deploy/build_linux.sh
- name: 'Pack installer' - name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_Linux_installer.tar name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
path: deploy/AmneziaVPN_Linux_Installer.tar path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
@@ -84,7 +93,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
env: env:
QT_VERSION: 6.6.2 QT_VERSION: 6.10.1
QIF_VERSION: 4.7 QIF_VERSION: 4.7
BUILD_ARCH: 64 BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -102,8 +111,16 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' - name: 'Get version from CMakeLists.txt'
uses: hendrikmuhs/ccache-action@v1.2 id: get_version
shell: bash
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install Qt' - name: 'Install Qt'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
@@ -111,32 +128,62 @@ jobs:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
host: 'windows' host: 'windows'
target: 'desktop' target: 'desktop'
arch: 'win64_msvc2019_64' arch: 'win64_msvc2022_64'
modules: 'qtremoteobjects qt5compat qtshadertools' modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true' setup-python: 'true'
tools: 'tools_ifw' tools: 'tools_ifw'
set-env: 'true' set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}' aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Setup mvsc' - name: 'Setup mvsc'
uses: ilammy/msvc-dev-cmd@v1 uses: ilammy/msvc-dev-cmd@v1
with: with:
arch: 'x64' arch: 'x64'
- name: 'Setup .NET SDK'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: 'Install WiX Toolset'
shell: powershell
run: |
dotnet tool install --global wix --version 4.0.6
wix extension add -g WixToolset.UI.wixext/4.0.6
wix extension add -g WixToolset.Util.wixext/4.0.6
wix extension list -g
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: 'Build project' - name: 'Build project'
shell: cmd shell: cmd
run: | run: |
set BUILD_ARCH=${{ env.BUILD_ARCH }} set BUILD_ARCH=${{ env.BUILD_ARCH }}
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin" set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2022_64\\bin"
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin" set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
call deploy\\build_windows.bat call deploy\\build_windows.bat
- name: 'Rename Windows installer'
shell: cmd
run: |
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_Windows_installer name: AmneziaVPN_${{ env.VERSION }}_x64.exe
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe path: AmneziaVPN_${{ env.VERSION }}_x64.exe
retention-days: 7
- name: 'Upload MSI installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Windows_MSI_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
@@ -149,10 +196,10 @@ jobs:
# ------------------------------------------------------ # ------------------------------------------------------
Build-iOS: Build-iOS:
runs-on: macos-13 runs-on: macos-latest
env: env:
QT_VERSION: 6.6.2 QT_VERSION: 6.10.1
CC: cc CC: cc
CXX: c++ CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -167,7 +214,7 @@ jobs:
- name: 'Setup xcode' - name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1 uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: '15.2' xcode-version: '26.1'
- name: 'Install desktop Qt' - name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
@@ -211,8 +258,8 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install dependencies' - name: 'Install dependencies'
run: pip install jsonschema jinja2 run: pip install jsonschema jinja2
@@ -303,8 +350,8 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project' - name: 'Build project'
run: | run: |
@@ -331,7 +378,7 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
env: env:
QT_VERSION: 6.8.0 QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }} MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -361,7 +408,7 @@ jobs:
xcode-version: '16.2.0' xcode-version: '16.2.0'
- name: 'Install Qt' - name: 'Install Qt'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v4
with: with:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
host: 'mac' host: 'mac'
@@ -371,8 +418,9 @@ jobs:
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true' setup-python: 'true'
set-env: 'true' set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}' aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Get sources' - name: 'Get sources'
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -380,19 +428,32 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' - name: 'Get version from CMakeLists.txt'
uses: hendrikmuhs/ccache-action@v1.2 id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project' - name: 'Build project'
run: | run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin" export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos.sh -n bash deploy/build_macos.sh -n
- name: 'Pack macOS installer'
run: |
cd deploy/build/pkg
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
cd ../../..
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN_MacOS_installer name: AmneziaVPN_${{ env.VERSION }}_macos.zip
path: deploy/build/pkg/AmneziaVPN.pkg path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
@@ -406,7 +467,7 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
env: env:
QT_VERSION: 6.8.3 QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }} MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -426,21 +487,31 @@ jobs:
- name: 'Setup xcode' - name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1 uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: '16.2.0' xcode-version: '26.1'
- name: 'Install Qt' - name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
with: with:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
host: 'mac' host: 'mac'
target: 'desktop' target: 'desktop'
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
arch: 'clang_64' arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true' set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}' extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: false
- name: 'Setup gomobile'
run: |
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
- name: 'Get sources' - name: 'Get sources'
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -448,8 +519,8 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project' - name: 'Build project'
run: | run: |
@@ -466,11 +537,11 @@ jobs:
# ------------------------------------------------------ # ------------------------------------------------------
Build-Android: Build-Android:
runs-on: ubuntu-latest runs-on: android-runner
env: env:
ANDROID_BUILD_PLATFORM: android-36 ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.9.3 QT_VERSION: 6.10.1
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -551,15 +622,22 @@ jobs:
with: with:
submodules: 'true' submodules: 'true'
- name: 'Setup ccache' - name: 'Get version from CMakeLists.txt'
uses: hendrikmuhs/ccache-action@v1.2 id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup Java' - name: 'Setup Java'
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
cache: 'gradle' # cache: 'gradle'
- name: 'Setup Android NDK' - name: 'Setup Android NDK'
id: setup-ndk id: setup-ndk
@@ -584,35 +662,44 @@ jobs:
shell: bash shell: bash
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }} run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
- name: 'Rename Android APKs'
run: |
cd deploy/build
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
cd ../..
- name: 'Upload x86_64 apk' - name: 'Upload x86_64 apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN-android-x86_64 name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
path: deploy/build/AmneziaVPN-x86_64-release.apk path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
compression-level: 0 compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload x86 apk' - name: 'Upload x86 apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN-android-x86 name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
path: deploy/build/AmneziaVPN-x86-release.apk path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
compression-level: 0 compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload arm64-v8a apk' - name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN-android-arm64-v8a name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
compression-level: 0 compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload armeabi-v7a apk' - name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: AmneziaVPN-android-armeabi-v7a name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
compression-level: 0 compression-level: 0
retention-days: 7 retention-days: 7
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: Verify git tag - name: Verify git tag
run: | run: |
TAG_NAME=${{ inputs.RELEASE_VERSION }} TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/') CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)." echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else else
+3
View File
@@ -140,3 +140,6 @@ ios-ne-build.sh
macos-ne-build.sh macos-ne-build.sh
macos-signed-build.sh macos-signed-build.sh
macos-with-sign-build.sh macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12
+4
View File
@@ -14,3 +14,7 @@
[submodule "client/3rd/QSimpleCrypto"] [submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6
+38 -2
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.11.1) set(AMNEZIAVPN_VERSION 4.8.14.6)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION} project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2096) set(APP_ANDROID_VERSION_CODE 2118)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
@@ -49,3 +49,39 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake) include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif() endif()
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
set(CPACK_GENERATOR "WIX")
set(CPACK_WIX_VERSION 4)
set(CPACK_PACKAGE_NAME "AmneziaVPN")
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
# WiX patches
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
# WiX v4 Util extension for CloseApplication + namespace for util
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
include(CPack)
endif()
Vendored Submodule
+1
Submodule client/3rd/qtgamepad added at f72b3e0c62
+12 -3
View File
@@ -56,10 +56,9 @@ target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
) )
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
endif() endif()
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
@@ -79,6 +78,7 @@ set(AMNEZIAVPN_TS_FILES
) )
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES}) qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
@@ -228,4 +228,13 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
endif() endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC}) target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
qt_finalize_target(${PROJECT})
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${PROJECT})
else()
qt_finalize_target(${PROJECT})
endif()
+71 -4
View File
@@ -13,6 +13,8 @@
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
#include <QEvent> #include <QEvent>
#include <QDir>
#include <QSettings>
#include "logger.h" #include "logger.h"
#include "ui/controllers/pageController.h" #include "ui/controllers/pageController.h"
@@ -25,10 +27,15 @@
#include <QtQuick/QQuickWindow> // for QQuickWindow #include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*> #include <QWindow> // for qobject_cast<QWindow*>
bool AmneziaApplication::m_forceQuit = false;
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_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")) m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
{ {
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false); setQuitOnLastWindowClosed(false);
// Fix config file permissions // Fix config file permissions
@@ -53,14 +60,40 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication() AmneziaApplication::~AmneziaApplication()
{ {
#ifdef AMNEZIA_DESKTOP
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
}
#endif
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit(); m_vpnConnectionThread.quit();
if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
}
if (m_engine) { if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine; delete m_engine;
} }
} }
#ifdef Q_OS_ANDROID
namespace {
static void clearQtCaches()
{
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
if (!cacheRoot.isEmpty()) {
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
QDir(cacheRoot + "/qmlcache").removeRecursively();
}
}
}
#endif
void AmneziaApplication::init() void AmneziaApplication::init()
{ {
m_engine = new QQmlApplicationEngine; m_engine = new QQmlApplicationEngine;
@@ -96,6 +129,16 @@ void AmneziaApplication::init()
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine)); m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_engine->addImportPath("qrc:/ui/qml/Modules/"); m_engine->addImportPath("qrc:/ui/qml/Modules/");
if (m_parser.isSet(m_optImport)) {
const QString data = m_parser.value(m_optImport);
if (!data.isEmpty()) {
if (m_coreController) {
m_coreController->importConfigFromData(data);
}
}
}
m_engine->load(url); m_engine->load(url);
m_coreController->setQmlRoot(); m_coreController->setQmlRoot();
@@ -135,6 +178,18 @@ void AmneziaApplication::init()
} }
}); });
#endif #endif
if (m_parser.isSet(m_optConnect)) {
bool ok = false;
int idx = m_parser.value(m_optConnect).toInt(&ok);
if (ok) {
QTimer::singleShot(0, this, [this, idx]() {
if (m_coreController) {
m_coreController->openConnectionByIndex(idx);
}
});
}
}
} }
void AmneziaApplication::registerTypes() void AmneziaApplication::registerTypes()
@@ -181,6 +236,8 @@ bool AmneziaApplication::parseCommands()
m_parser.addOption(m_optAutostart); m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup); m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
m_parser.process(*this); m_parser.process(*this);
@@ -217,8 +274,12 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit(); quit();
#else #else
if (m_coreController && m_coreController->pageController()) { if (m_forceQuit) {
m_coreController->pageController()->hideMainWindow(); quit();
} else {
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
} }
#endif #endif
return true; // eat the close return true; // eat the close
@@ -227,6 +288,12 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
return QObject::eventFilter(watched, event); return QObject::eventFilter(watched, event);
} }
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{ {
return m_engine; return m_engine;
+6
View File
@@ -45,7 +45,11 @@ public:
QNetworkAccessManager *networkManager(); QNetworkAccessManager *networkManager();
QClipboard *getClipboard(); QClipboard *getClipboard();
public slots:
void forceQuit();
private: private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {}; QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
@@ -58,6 +62,8 @@ private:
QCommandLineOption m_optAutostart; QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup; QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection; QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread; QThread m_vpnConnectionThread;
+2 -2
View File
@@ -45,7 +45,7 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc" |fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:windowSoftInputMode="stateUnchanged|adjustResize" android:windowSoftInputMode="adjustResize|stateUnchanged"
android:enableOnBackInvokedCallback="false" android:enableOnBackInvokedCallback="false"
android:exported="true"> android:exported="true">
@@ -215,4 +215,4 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" /> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider> </provider>
</application> </application>
</manifest> </manifest>
@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
openVpnClient = null openVpnClient = null
} }
override fun reconnectVpn(vpnBuilder: Builder) { override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
openVpnClient?.let { openVpnClient?.let {
it.establish = makeEstablish(vpnBuilder) it.establish = makeEstablish(vpnBuilder)
it.reconnect(0) it.reconnect(0)
@@ -42,7 +42,7 @@ abstract class Protocol {
abstract fun stopVpn() abstract fun stopVpn()
abstract fun reconnectVpn(vpnBuilder: Builder) abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) { protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
if (!allowSplitTunneling) { if (!allowSplitTunneling) {
@@ -26,6 +26,8 @@ import android.os.ParcelFileDescriptor
import android.os.SystemClock import android.os.SystemClock
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.provider.Settings import android.provider.Settings
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -35,6 +37,10 @@ import android.widget.Toast
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import java.io.IOException import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
@@ -69,6 +75,8 @@ private const val OPEN_FILE_ACTION_CODE = 3
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4 private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED" private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
private const val OPEN_FILE_AFTER_RESUME_DELAY_MS = 400L
private const val KEY_PENDING_OPEN_FILE_URI = "pending_open_file_uri"
class AmneziaActivity : QtActivity() { class AmneziaActivity : QtActivity() {
@@ -85,6 +93,12 @@ class AmneziaActivity : QtActivity() {
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>() private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>() private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
private var isActivityResumed = false
private var hasWindowFocus = false
private val resumeHandler = Handler(Looper.getMainLooper())
private var pendingOpenFileUri: String? = null
private var openFileDeliveryScheduled = false
private val vpnServiceEventHandler: Handler by lazy(NONE) { private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) { object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) { override fun handleMessage(msg: Message) {
@@ -186,11 +200,18 @@ class AmneziaActivity : QtActivity() {
doBindService() doBindService()
} }
) )
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
openFileDeliveryScheduled = false
registerBroadcastReceivers() registerBroadcastReceivers()
intent?.let(::processIntent) intent?.let(::processIntent)
runBlocking { vpnProto = proto.await() } runBlocking { vpnProto = proto.await() }
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
pendingOpenFileUri?.let { outState.putString(KEY_PENDING_OPEN_FILE_URI, it) }
}
private fun loadLibs() { private fun loadLibs() {
listOf( listOf(
"rsapss", "rsapss",
@@ -256,6 +277,11 @@ class AmneziaActivity : QtActivity() {
} }
override fun onStop() { override fun onStop() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity stops
resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Stop Amnezia activity") Log.d(TAG, "Stop Amnezia activity")
doUnbindService() doUnbindService()
mainScope.launch { mainScope.launch {
@@ -265,23 +291,102 @@ class AmneziaActivity : QtActivity() {
super.onStop() super.onStop()
} }
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
hasWindowFocus = hasFocus
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
// Cancel pending operations if window loses focus
if (!hasFocus) {
resumeHandler.removeCallbacksAndMessages(null)
}
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN
when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT -> {
nativeGamepadKeyEvent(0, keyCode, pressed)
return true
}
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT -> {
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
val synthetic = KeyEvent(
event.downTime, event.eventTime, event.action, syntheticKeyCode,
event.repeatCount, event.metaState, -1, event.scanCode,
event.flags, InputDevice.SOURCE_KEYBOARD
)
return super.dispatchKeyEvent(synthetic)
}
}
return super.dispatchKeyEvent(event)
}
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() {
super.onPause()
isActivityResumed = false
// Cancel all pending operations when activity pauses
resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Pause Amnezia activity")
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
isActivityResumed = true
Log.d(TAG, "Resume Amnezia activity")
if (pendingOpenFileUri != null && !openFileDeliveryScheduled) {
val uri = pendingOpenFileUri!!
openFileDeliveryScheduled = true
resumeHandler.postDelayed({
if (!isFinishing && !isDestroyed) {
pendingOpenFileUri = null
openFileDeliveryScheduled = false
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
}, OPEN_FILE_AFTER_RESUME_DELAY_MS)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply { window.decorView.apply {
invalidate() invalidate()
postDelayed({ resumeHandler.postDelayed({
sendTouch(1f, 1f) // Check if activity is still resumed and has focus before executing
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(1f, 1f)
}
}, 100) }, 100)
postDelayed({ resumeHandler.postDelayed({
sendTouch(2f, 2f) if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
}
}, 200) }, 200)
postDelayed({ resumeHandler.postDelayed({
requestLayout() if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
invalidate() requestLayout()
invalidate()
}
}, 250) }, 250)
} }
} }
@@ -300,15 +405,57 @@ class AmneziaActivity : QtActivity() {
isAppearanceLightStatusBars = false isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false isAppearanceLightNavigationBars = false
} }
// Workaround for Android 14 (API 34+) IME adjustResize bug
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
setupImeInsetsListener()
}
} else { } else {
window.apply { window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black) statusBarColor = getColor(R.color.black)
} }
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
}
}
private fun setupImeInsetsListener() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = if (imeVisible) imeInsets.bottom else 0
val density = resources.displayMetrics.density
val imeHeightDp = (imeHeight / density).toInt()
// Also track system bars (navigation bar, status bar) changes
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val navBarHeight = systemBarsInsets.bottom
val navBarHeightDp = (navBarHeight / density).toInt()
val statusBarHeight = systemBarsInsets.top
val statusBarHeightDp = (statusBarHeight / density).toInt()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onImeInsetsChanged(imeHeightDp)
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
}
// Return windowInsets instead of CONSUMED to allow proper handling
windowInsets
} }
} }
override fun onDestroy() { override fun onDestroy() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity is destroyed
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Destroy Amnezia activity") Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver) unregisterBroadcastReceiver(notificationStateReceiver)
notificationStateReceiver = null notificationStateReceiver = null
@@ -634,9 +781,13 @@ class AmneziaActivity : QtActivity() {
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION) grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}?.toString() ?: "" }?.toString() ?: ""
Log.v(TAG, "Open file: $uri") Log.v(TAG, "Open file: $uri")
mainScope.launch { if (uri.isNotEmpty()) {
qtInitialized.await() pendingOpenFileUri = uri
QtAndroidController.onFileOpened(uri) } else {
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
} }
} }
)) ))
@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
protocolState.value = RECONNECTING protocolState.value = RECONNECTING
connectionJob = connectionScope.launch { connectionJob = connectionScope.launch {
vpnProto?.protocol?.reconnectVpn(Builder()) vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
} }
} }
@@ -1,7 +1,10 @@
package org.amnezia.vpn package org.amnezia.vpn
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -11,8 +14,29 @@ private const val TAG = "TvFilePicker"
class TvFilePicker : ComponentActivity() { class TvFilePicker : ComponentActivity() {
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
setResult(RESULT_OK, Intent().apply { data = it }) override fun createIntent(context: Context, input: Array<String>): Intent {
val intent = super.createIntent(context, input)
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activitiesToResolveIntent.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
throw ActivityNotFoundException()
}
return intent
}
}) {
setResult(RESULT_OK, Intent().apply {
data = it
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
finish() finish()
} }
@@ -31,7 +55,7 @@ class TvFilePicker : ComponentActivity() {
private fun getFile() { private fun getFile() {
try { try {
Log.v(TAG, "getFile") Log.v(TAG, "getFile")
fileChooseResultLauncher.launch("*/*") fileChooseResultLauncher.launch(arrayOf("*/*"))
} catch (_: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found") Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) }) setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
@@ -28,4 +28,7 @@ object QtAndroidController {
external fun onAuthResult(result: Boolean) external fun onAuthResult(result: Boolean)
external fun decodeQrCode(data: String): Boolean external fun decodeQrCode(data: String): Boolean
external fun onImeInsetsChanged(heightDp: Int)
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
} }
@@ -12,6 +12,7 @@ import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
@@ -27,6 +28,7 @@ private const val TAG = "Wireguard"
open class Wireguard : Protocol() { open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1 private var tunnelHandle: Int = -1
private var config: WireguardConfig? = null // save config for reconnect
protected open val ifName: String = "amn0" protected open val ifName: String = "amn0"
private lateinit var scope: CoroutineScope private lateinit var scope: CoroutineScope
private var statusJob: Job? = null private var statusJob: Job? = null
@@ -61,6 +63,7 @@ open class Wireguard : Protocol() {
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) { override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config) val wireguardConfig = parseConfig(config)
start(wireguardConfig, vpnBuilder, protect) start(wireguardConfig, vpnBuilder, protect)
this.config = wireguardConfig
} }
protected open fun parseConfig(config: JSONObject): WireguardConfig { protected open fun parseConfig(config: JSONObject): WireguardConfig {
@@ -122,23 +125,24 @@ open class Wireguard : Protocol() {
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) } configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) } configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) } configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) } configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) } configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) } configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) } configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
configData.optStringOrNull("I1")?.let { setI1(it) } configData.optStringOrNull("I1")?.let { setI1(it) }
configData.optStringOrNull("I2")?.let { setI2(it) } configData.optStringOrNull("I2")?.let { setI2(it) }
configData.optStringOrNull("I3")?.let { setI3(it) } configData.optStringOrNull("I3")?.let { setI3(it) }
configData.optStringOrNull("I4")?.let { setI4(it) } configData.optStringOrNull("I4")?.let { setI4(it) }
configData.optStringOrNull("I5")?.let { setI5(it) } configData.optStringOrNull("I5")?.let { setI5(it) }
configData.optStringOrNull("J1")?.let { setJ1(it) }
configData.optStringOrNull("J2")?.let { setJ2(it) }
configData.optStringOrNull("J3")?.let { setJ3(it) }
configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
} }
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) { private fun start(
if (tunnelHandle != -1) { config: WireguardConfig,
vpnBuilder: Builder,
protect: (Int) -> Boolean,
stopExistingVpn: Boolean = false
) {
if (!stopExistingVpn && tunnelHandle != -1) {
Log.w(TAG, "Tunnel already up") Log.w(TAG, "Tunnel already up")
return return
} }
@@ -146,6 +150,9 @@ open class Wireguard : Protocol() {
buildVpnInterface(config, vpnBuilder) buildVpnInterface(config, vpnBuilder)
vpnBuilder.establish().use { tunFd -> vpnBuilder.establish().use { tunFd ->
if (stopExistingVpn && tunnelHandle != -1) {
turnOffVpn()
}
if (tunFd == null) { if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked") throw VpnStartException("Create VPN interface: permission not granted or revoked")
} }
@@ -202,20 +209,25 @@ open class Wireguard : Protocol() {
return lastHandshake return lastHandshake
} }
override fun stopVpn() { private fun turnOffVpn() {
if (tunnelHandle == -1) {
Log.w(TAG, "Tunnel already down")
return
}
statusJob?.cancel() statusJob?.cancel()
statusJob = null statusJob = null
val handleToClose = tunnelHandle val handleToClose = tunnelHandle
tunnelHandle = -1 tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose) GoBackend.awgTurnOff(handleToClose)
}
override fun stopVpn() {
if (tunnelHandle == -1) {
Log.w(TAG, "Tunnel already down")
return
}
turnOffVpn()
state.value = DISCONNECTED state.value = DISCONNECTED
} }
override fun reconnectVpn(vpnBuilder: Builder) { override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
state.value = CONNECTED val config = this.config ?: throw VpnException("Reconnect config is empty")
start(config, vpnBuilder, protect, true)
} }
} }
@@ -22,19 +22,15 @@ open class WireguardConfig protected constructor(
val s2: Int?, val s2: Int?,
val s3: Int?, val s3: Int?,
val s4: Int?, val s4: Int?,
val h1: Long?, val h1: String?,
val h2: Long?, val h2: String?,
val h3: Long?, val h3: String?,
val h4: Long?, val h4: String?,
var i1: String?, var i1: String?,
var i2: String?, var i2: String?,
var i3: String?, var i3: String?,
var i4: String?, var i4: String?,
var i5: String?, var i5: String?,
var j1: String?,
var j2: String?,
var j3: String?,
var itime: Int?
) : ProtocolConfig(protocolConfigBuilder) { ) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this( protected constructor(builder: Builder) : this(
@@ -61,10 +57,6 @@ open class WireguardConfig protected constructor(
builder.i3, builder.i3,
builder.i4, builder.i4,
builder.i5, builder.i5,
builder.j1,
builder.j2,
builder.j3,
builder.itime
) )
fun toWgUserspaceString(): String = with(StringBuilder()) { fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -94,10 +86,6 @@ open class WireguardConfig protected constructor(
i3?.let { appendLine("i3=$it") } i3?.let { appendLine("i3=$it") }
i4?.let { appendLine("i4=$it") } i4?.let { appendLine("i4=$it") }
i5?.let { appendLine("i5=$it") } i5?.let { appendLine("i5=$it") }
j1?.let { appendLine("j1=$it") }
j2?.let { appendLine("j2=$it") }
j3?.let { appendLine("j3=$it") }
itime?.let { appendLine("itime=$it") }
} }
} }
@@ -152,19 +140,15 @@ open class WireguardConfig protected constructor(
internal var s2: Int? = null internal var s2: Int? = null
internal var s3: Int? = null internal var s3: Int? = null
internal var s4: Int? = null internal var s4: Int? = null
internal var h1: Long? = null internal var h1: String? = null
internal var h2: Long? = null internal var h2: String? = null
internal var h3: Long? = null internal var h3: String? = null
internal var h4: Long? = null internal var h4: String? = null
internal var i1: String? = null internal var i1: String? = null
internal var i2: String? = null internal var i2: String? = null
internal var i3: String? = null internal var i3: String? = null
internal var i4: String? = null internal var i4: String? = null
internal var i5: String? = null internal var i5: String? = null
internal var j1: String? = null
internal var j2: String? = null
internal var j3: String? = null
internal var itime: Int? = null
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint } fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
@@ -185,19 +169,15 @@ open class WireguardConfig protected constructor(
fun setS2(s2: Int) = apply { this.s2 = s2 } fun setS2(s2: Int) = apply { this.s2 = s2 }
fun setS3(s3: Int) = apply { this.s3 = s3 } fun setS3(s3: Int) = apply { this.s3 = s3 }
fun setS4(s4: Int) = apply { this.s4 = s4 } fun setS4(s4: Int) = apply { this.s4 = s4 }
fun setH1(h1: Long) = apply { this.h1 = h1 } fun setH1(h1: String) = apply { this.h1 = h1 }
fun setH2(h2: Long) = apply { this.h2 = h2 } fun setH2(h2: String) = apply { this.h2 = h2 }
fun setH3(h3: Long) = apply { this.h3 = h3 } fun setH3(h3: String) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 } fun setH4(h4: String) = apply { this.h4 = h4 }
fun setI1(i1: String) = apply { this.i1 = i1 } fun setI1(i1: String) = apply { this.i1 = i1 }
fun setI2(i2: String) = apply { this.i2 = i2 } fun setI2(i2: String) = apply { this.i2 = i2 }
fun setI3(i3: String) = apply { this.i3 = i3 } fun setI3(i3: String) = apply { this.i3 = i3 }
fun setI4(i4: String) = apply { this.i4 = i4 } fun setI4(i4: String) = apply { this.i4 = i4 }
fun setI5(i5: String) = apply { this.i5 = i5 } fun setI5(i5: String) = apply { this.i5 = i5 }
fun setJ1(j1: String) = apply { this.j1 = j1 }
fun setJ2(j2: String) = apply { this.j2 = j2 }
fun setJ3(j3: String) = apply { this.j3 = j3 }
fun setItime(itime: Int) = apply { this.itime = itime }
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) } override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
} }
+2 -2
View File
@@ -157,7 +157,7 @@ class Xray : Protocol() {
state.value = DISCONNECTED state.value = DISCONNECTED
} }
override fun reconnectVpn(vpnBuilder: Builder) { override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
state.value = CONNECTED state.value = CONNECTED
} }
@@ -166,7 +166,7 @@ class Xray : Protocol() {
mtu = config.mtu.toLong() mtu = config.mtu.toLong()
proxy = "socks5://127.0.0.1:${config.socksPort}" proxy = "socks5://127.0.0.1:${config.socksPort}"
device = "fd://$fd" device = "fd://$fd"
logLevel = "warning" logLevel = "warn"
} }
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err -> LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
throw VpnStartException("Failed to start tun2socks: $err") throw VpnStartException("Failed to start tun2socks: $err")
+22 -2
View File
@@ -38,7 +38,7 @@ elseif(APPLE AND NOT IOS)
endif() endif()
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include") set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a") set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a") set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
elseif(IOS) elseif(IOS)
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64") set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a") set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
@@ -62,7 +62,7 @@ elseif(LINUX)
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a") set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a") set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
endif() endif()
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH} file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
DESTINATION ${OPENSSL_LIBRARIES_DIR}) DESTINATION ${OPENSSL_LIBRARIES_DIR})
@@ -83,6 +83,26 @@ add_compile_definitions(_WINSOCKAPI_)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_WITH_QT6 ON) set(BUILD_WITH_QT6 ON)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain) add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
if(ANDROID)
# Use qtgamepad from amnezia-vpn/qtgamepad repository
# Only if Qt6CorePrivate is available (required by qtgamepad)
find_package(Qt6CorePrivate CONFIG QUIET)
if(Qt6CorePrivate_FOUND)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
# Link both the C++ module and QML plugin
if(TARGET GamepadLegacy)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
endif()
if(TARGET GamepadLegacyQuickPrivate)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
endif()
message(STATUS "Gamepad support enabled for Android")
else()
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
endif()
endif()
set(LIBS ${LIBS} qt6keychain) set(LIBS ${LIBS} qt6keychain)
include_directories( include_directories(
+5 -1
View File
@@ -20,7 +20,11 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's # We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api # As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics) # Check if Qt6::CorePrivate is available (may not be in all Qt versions/configurations)
if(TARGET Qt6::CorePrivate)
set(LIBS ${LIBS} Qt6::CorePrivate)
endif()
set(LIBS ${LIBS} -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android) link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
+2
View File
@@ -34,6 +34,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.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/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.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_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
@@ -46,6 +47,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
) )
+3 -1
View File
@@ -35,6 +35,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h ${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/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
) )
@@ -45,6 +46,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm ${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/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
@@ -161,7 +163,7 @@ add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks $<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution" COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter" "$<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} COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework" COMMENT "Signing OpenVPNAdapter framework"
+16 -5
View File
@@ -28,6 +28,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/../common/logger/logger.h ${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/utils/qmlUtils.h ${CLIENT_ROOT_DIR}/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/api/apiUtils.h ${CLIENT_ROOT_DIR}/core/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
) )
# Mozilla headres # Mozilla headres
@@ -36,7 +37,6 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h ${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h ${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
) )
if(NOT IOS AND NOT MACOS_NE) if(NOT IOS AND NOT MACOS_NE)
@@ -79,6 +79,7 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp ${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp ${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp ${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp
) )
# Mozilla sources # Mozilla sources
@@ -86,7 +87,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp ${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp ${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
) )
if(NOT IOS AND NOT MACOS_NE) if(NOT IOS AND NOT MACOS_NE)
@@ -175,13 +175,12 @@ if(WIN32)
) )
endif() endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
message("Client desktop build") message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP) add_compile_definitions(AMNEZIA_DESKTOP)
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/ipcclient.h ${CLIENT_ROOT_DIR}/core/ipcclient.h
${CLIENT_ROOT_DIR}/core/privileged_process.h
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
@@ -189,11 +188,12 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h ${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h ${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h ${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
) )
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/ipcclient.cpp ${CLIENT_ROOT_DIR}/core/ipcclient.cpp
${CLIENT_ROOT_DIR}/core/privileged_process.cpp ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
@@ -203,3 +203,14 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp ${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
) )
endif() endif()
if(APPLE AND MACOS_NE)
# Include only the tray notification handler in NE builds
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
)
endif()
+9 -11
View File
@@ -41,18 +41,16 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
// jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize); if (container == DockerContainer::Awg2) {
// jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize); jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
}
// jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1); jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
// jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2); jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
// jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3); jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
// jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4); jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
// jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5); jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
// jsonConfig[config_key::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1);
// jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2);
// jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3);
// jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout);
jsonConfig[config_key::mtu] = jsonConfig[config_key::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu); containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
@@ -103,7 +103,11 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData; return connData;
} }
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath); QString configPath = m_serverConfigPath;
if (container == DockerContainer::Awg) {
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
}
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n"; stdOut += data + "\n";
@@ -161,15 +165,18 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
"AllowedIPs = %3/32\n\n") "AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP); .arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
libssh::ScpOverwriteMode::ScpAppendToExisting); libssh::ScpOverwriteMode::ScpAppendToExisting);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
return connData; return connData;
} }
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'") bool isAwg = (container == DockerContainer::Awg2);
.arg(m_serverConfigPath); QString bin = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
QString script = QString(
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
errorCode = m_serverController->runScript( errorCode = m_serverController->runScript(
credentials, credentials,
+39 -8
View File
@@ -28,7 +28,10 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c)
return "none"; return "none";
if (c == DockerContainer::Cloak) if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak"; return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg)
return "amnezia-awg";
if (c == DockerContainer::Awg2)
return "amnezia-awg2";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>(); QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c)); QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -41,7 +44,10 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
return "none"; return "none";
if (c == DockerContainer::Ipsec) if (c == DockerContainer::Ipsec)
return "ikev2"; return "ikev2";
if (c == DockerContainer::Awg)
return "awg";
if (c == DockerContainer::Awg2)
return "awg";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>(); QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c)); QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -71,6 +77,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy }; case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
case DockerContainer::Awg: return { Proto::Awg };
case DockerContainer::Awg2: return { Proto::Awg };
default: return { defaultProtocol(container) }; default: return { defaultProtocol(container) };
} }
} }
@@ -94,6 +102,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{ DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" }, { DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" },
{ DockerContainer::Xray, "XRay" }, { DockerContainer::Xray, "XRay" },
{ DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::Ipsec, QObject::tr("IPsec") },
{ DockerContainer::SSXray, "Shadowsocks"}, { DockerContainer::SSXray, "Shadowsocks"},
@@ -120,6 +129,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Awg, { DockerContainer::Awg,
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. " QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") }, "It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
{ DockerContainer::Awg2,
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
{ DockerContainer::Xray, { DockerContainer::Xray,
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. " QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
"It is highly resistant to detection and offers high speed.") }, "It is highly resistant to detection and offers high speed.") },
@@ -182,7 +194,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Minimal configuration required\n" "* Minimal configuration required\n"
"* Easily detected by DPI systems (susceptible to blocking)\n" "* Easily detected by DPI systems (susceptible to blocking)\n"
"* Operates over UDP protocol") }, "* Operates over UDP protocol") },
{ DockerContainer::Awg, { DockerContainer::Awg2,
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, " QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
"combining simplified architecture with high performance across all devices. " "combining simplified architecture with high performance across all devices. "
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, " "It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
@@ -242,6 +254,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
case DockerContainer::Cloak: return Proto::Cloak; case DockerContainer::Cloak: return Proto::Cloak;
case DockerContainer::ShadowSocks: return Proto::ShadowSocks; case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
case DockerContainer::WireGuard: return Proto::WireGuard; case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg; case DockerContainer::Awg: return Proto::Awg;
case DockerContainer::Xray: return Proto::Xray; case DockerContainer::Xray: return Proto::Xray;
case DockerContainer::Ipsec: return Proto::Ikev2; case DockerContainer::Ipsec: return Proto::Ikev2;
@@ -255,6 +268,15 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
} }
} }
QString ContainerProps::containerTypeToProtocolString(DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
Proto p = defaultProtocol(c);
return ProtocolProps::protoToString(p);
}
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
{ {
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
@@ -265,6 +287,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
switch (c) { switch (c) {
case DockerContainer::WireGuard: return true; case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true; case DockerContainer::OpenVpn: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true; case DockerContainer::Xray: return true;
case DockerContainer::Cloak: return true; case DockerContainer::Cloak: return true;
@@ -278,6 +301,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
// macOS build using Network Extension hide OpenVPN-based containers // macOS build using Network Extension hide OpenVPN-based containers
switch (c) { switch (c) {
case DockerContainer::WireGuard: return true; case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true; case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true; case DockerContainer::SSXray: return true;
@@ -300,6 +324,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::WireGuard: return true; case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true; case DockerContainer::OpenVpn: return true;
case DockerContainer::ShadowSocks: return false; case DockerContainer::ShadowSocks: return false;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
case DockerContainer::Cloak: return true; case DockerContainer::Cloak: return true;
case DockerContainer::Xray: return true; case DockerContainer::Xray: return true;
@@ -329,7 +354,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
bool ContainerProps::isEasySetupContainer(DockerContainer container) bool ContainerProps::isEasySetupContainer(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::Awg: return true; case DockerContainer::Awg2: return true;
default: return false; default: return false;
} }
} }
@@ -337,7 +362,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container) QString ContainerProps::easySetupHeader(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::Awg: return tr("Automatic"); case DockerContainer::Awg2: return tr("Automatic");
default: return ""; default: return "";
} }
} }
@@ -345,7 +370,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container) QString ContainerProps::easySetupDescription(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. " case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions."); "It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
default: return ""; default: return "";
} }
@@ -354,7 +379,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container) int ContainerProps::easySetupOrder(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::Awg: return 1; case DockerContainer::Awg2: return 1;
default: return 0; default: return 0;
} }
} }
@@ -370,6 +395,12 @@ bool ContainerProps::isShareable(DockerContainer container)
} }
} }
bool ContainerProps::isAwgContainer(DockerContainer container)
{
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
}
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig) QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{ {
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol)) QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
@@ -387,7 +418,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
case DockerContainer::Cloak: return 5; case DockerContainer::Cloak: return 5;
case DockerContainer::ShadowSocks: return 6; case DockerContainer::ShadowSocks: return 6;
case DockerContainer::WireGuard: return 2; case DockerContainer::WireGuard: return 2;
case DockerContainer::Awg: return 1; case DockerContainer::Awg2: return 1;
case DockerContainer::Xray: return 3; case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7; case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8; case DockerContainer::SSXray: return 8;
+5
View File
@@ -17,6 +17,7 @@ namespace amnezia
enum DockerContainer { enum DockerContainer {
None = 0, None = 0,
Awg, Awg,
Awg2,
WireGuard, WireGuard,
OpenVpn, OpenVpn,
Cloak, Cloak,
@@ -45,6 +46,7 @@ namespace amnezia
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c);
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers(); Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
@@ -71,6 +73,9 @@ namespace amnezia
static bool isShareable(amnezia::DockerContainer container); static bool isShareable(amnezia::DockerContainer container);
static bool isAwgContainer(amnezia::DockerContainer container);
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig); static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
static int installPageOrder(amnezia::DockerContainer container); static int installPageOrder(amnezia::DockerContainer container);
+1
View File
@@ -68,6 +68,7 @@ namespace apiDefs
constexpr QLatin1String migrationCode("migration_code"); constexpr QLatin1String migrationCode("migration_code");
constexpr QLatin1String transactionId("transaction_id"); constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String isTestPurchase("is_test_purchase");
constexpr QLatin1String userCountryCode("user_country_code"); constexpr QLatin1String userCountryCode("user_country_code");
+14 -2
View File
@@ -1,6 +1,7 @@
#include "apiUtils.h" #include "apiUtils.h"
#include <QDateTime> #include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
namespace namespace
@@ -88,6 +89,7 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
{ {
const int httpStatusCodeConflict = 409; const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404; const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501;
if (!sslErrors.empty()) { if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors; qDebug().noquote() << sslErrors;
@@ -106,10 +108,20 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
qDebug() << replyError; qDebug() << replyError;
qDebug() << replyErrorString; qDebug() << replyErrorString;
qDebug() << httpStatusCode; qDebug() << httpStatusCode;
if (httpStatusCode == httpStatusCodeConflict) {
int httpStatusFromBody = -1;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
httpStatusFromBody = jsonObj.value("http_status").toInt(-1);
}
if (httpStatusFromBody == httpStatusCodeConflict) {
return amnezia::ErrorCode::ApiConfigLimitError; return amnezia::ErrorCode::ApiConfigLimitError;
} else if (httpStatusCode == httpStatusCodeNotFound) { } else if (httpStatusFromBody == httpStatusCodeNotFound) {
return amnezia::ErrorCode::ApiNotFoundError; return amnezia::ErrorCode::ApiNotFoundError;
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
return amnezia::ErrorCode::ApiUpdateRequestError;
} }
return amnezia::ErrorCode::ApiConfigDownloadError; return amnezia::ErrorCode::ApiConfigDownloadError;
} }
+26 -27
View File
@@ -135,7 +135,7 @@ void CoreController::initControllers()
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings)); new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel)); m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
@@ -154,9 +154,6 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings)); m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
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_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get()); m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
} }
@@ -231,8 +228,6 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler(); initAutoConnectHandler();
initAmneziaDnsToggledHandler(); initAmneziaDnsToggledHandler();
initPrepareConfigHandler(); initPrepareConfigHandler();
initImportPremiumV2VpnKeyHandler();
initShowMigrationDrawerHandler();
initStrictKillSwitchHandler(); initStrictKillSwitchHandler();
} }
@@ -324,7 +319,7 @@ void CoreController::initContainerModelUpdateHandler()
&ContainersModel::updateModel); &ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() { connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
if (m_serversModel->hasServersFromGatewayApi()) { if (m_serversModel->hasServersFromGatewayApi()) {
m_apiNewsController->fetchNews(); m_apiNewsController->fetchNews(false);
} }
}); });
m_serversModel->resetModel(); m_serversModel->resetModel();
@@ -373,7 +368,11 @@ void CoreController::initPrepareConfigHandler()
return; return;
} }
if (!m_installController->isConfigValid()) { m_installController->validateConfig();
});
connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) {
if (!isValid) {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
return; return;
} }
@@ -382,25 +381,6 @@ void CoreController::initPrepareConfigHandler()
}); });
} }
void CoreController::initImportPremiumV2VpnKeyHandler()
{
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
m_importController->extractConfigFromData(vpnKey);
m_importController->importConfig();
emit m_apiPremV1MigrationController->migrationFinished();
});
}
void CoreController::initShowMigrationDrawerHandler()
{
QTimer::singleShot(1000, this, [this]() {
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
m_apiPremV1MigrationController->showMigrationDrawer();
}
});
}
void CoreController::initStrictKillSwitchHandler() void CoreController::initStrictKillSwitchHandler()
{ {
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(), connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
@@ -411,3 +391,22 @@ QSharedPointer<PageController> CoreController::pageController() const
{ {
return m_pageController; return m_pageController;
} }
void CoreController::openConnectionByIndex(int serverIndex)
{
if (m_serversModel) {
m_serversModel->setProcessedServerIndex(serverIndex);
m_serversModel->setDefaultServerIndex(serverIndex);
}
m_connectionController->toggleConnection();
}
void CoreController::importConfigFromData(const QString &data)
{
if (!m_importController)
return;
if (m_importController->extractConfigFromData(data)) {
m_importController->importConfig();
}
}
+3 -4
View File
@@ -11,7 +11,6 @@
#include "ui/controllers/api/apiConfigsController.h" #include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h" #include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
#include "ui/controllers/api/apiNewsController.h" #include "ui/controllers/api/apiNewsController.h"
#include "ui/controllers/appSplitTunnelingController.h" #include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h" #include "ui/controllers/allowedDnsController.h"
@@ -65,6 +64,9 @@ public:
QSharedPointer<PageController> pageController() const; QSharedPointer<PageController> pageController() const;
void setQmlRoot(); void setQmlRoot();
void openConnectionByIndex(int serverIndex);
void importConfigFromData(const QString &data);
signals: signals:
void translationsUpdated(); void translationsUpdated();
void websiteUrlChanged(const QString &newUrl); void websiteUrlChanged(const QString &newUrl);
@@ -90,8 +92,6 @@ private:
void initAutoConnectHandler(); void initAutoConnectHandler();
void initAmneziaDnsToggledHandler(); void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler(); void initPrepareConfigHandler();
void initImportPremiumV2VpnKeyHandler();
void initShowMigrationDrawerHandler();
void initStrictKillSwitchHandler(); void initStrictKillSwitchHandler();
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here? QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
@@ -119,7 +119,6 @@ private:
QScopedPointer<ApiSettingsController> m_apiSettingsController; QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController; QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
QScopedPointer<ApiNewsController> m_apiNewsController; QScopedPointer<ApiNewsController> m_apiNewsController;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
+278 -47
View File
@@ -1,8 +1,10 @@
#include "gatewayController.h" #include "gatewayController.h"
#include <algorithm> #include <algorithm>
#include <functional>
#include <random> #include <random>
#include <QCryptographicHash>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
@@ -39,6 +41,11 @@ namespace
constexpr QLatin1String errorResponsePattern3("Account not found."); constexpr QLatin1String errorResponsePattern3("Account not found.");
constexpr QLatin1String updateRequestResponsePattern("client version update is required"); constexpr QLatin1String updateRequestResponsePattern("client version update is required");
constexpr int httpStatusCodeNotFound = 404;
constexpr int httpStatusCodeConflict = 409;
constexpr int httpStatusCodeNotImplemented = 501;
} }
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs, GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
@@ -72,7 +79,11 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
QString host = QUrl(encRequestData.request.url()).host(); QString host = QUrl(encRequestData.request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host); QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) { if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip }); IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
if (!reply.waitForFinished(1000) || !reply.returnValue())
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
});
} }
} }
#endif #endif
@@ -107,7 +118,8 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING); encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey); EVP_PKEY_free(publicKey);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv, "", encRequestData.salt); encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
"", encRequestData.salt);
} catch (...) { } catch (...) {
Utils::logException(); Utils::logException();
qCritical() << "error when encrypting the request body"; qCritical() << "error when encrypting the request body";
@@ -123,6 +135,26 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
return encRequestData; return encRequestData;
} }
GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(const QByteArray &encryptedResponseBody,
QNetworkReply::NetworkError replyError, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
{
DecryptionResult result;
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
try {
QSimpleCrypto::QBlockCipher blockCipher;
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
result.isDecryptionSuccessful = true;
} catch (...) {
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
}
return result;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody) ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{ {
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload); EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
@@ -137,7 +169,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
QList<QSslError> sslErrors; QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec(QEventLoop::ExcludeUserInputEvents);
QByteArray encryptedResponseBody = reply->readAll(); QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString(); QString replyErrorString = reply->errorString();
@@ -146,19 +178,27 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
reply->deleteLater(); reply->deleteLater();
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) { auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) { auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
encRequestData.request.setUrl(url); encRequestData.request.setUrl(url);
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody); return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
}; };
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData, auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) { &decryptionResult, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = reply->readAll(); encryptedResponseBody = reply->readAll();
replyErrorString = reply->errorString(); replyErrorString = reply->errorString();
replyError = reply->error(); replyError = reply->error();
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (!sslErrors.isEmpty() || shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (!sslErrors.isEmpty()
|| shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
sslErrors = nestedSslErrors; sslErrors = nestedSslErrors;
return false; return false;
} }
@@ -170,20 +210,19 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction); bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
} }
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody); auto errorCode =
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
if (errorCode) { if (errorCode) {
return errorCode; return errorCode;
} }
try { if (!decryptionResult.isDecryptionSuccessful) {
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();
qCritical() << "error when decrypting the request body"; qCritical() << "error when decrypting the request body";
return ErrorCode::ApiConfigDecryptionError; return ErrorCode::ApiConfigDecryptionError;
} }
responseBody = decryptionResult.decryptedBody;
return ErrorCode::NoError;
} }
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload) QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
@@ -202,11 +241,9 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
auto sslErrors = QSharedPointer<QList<QSslError>>::create(); auto sslErrors = QSharedPointer<QList<QSslError>>::create();
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
*sslErrors = errors;
});
connect(reply, &QNetworkReply::finished, reply, [=]() { connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
QByteArray encryptedResponseBody = reply->readAll(); QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString(); QString replyErrorString = reply->errorString();
auto replyError = reply->error(); auto replyError = reply->error();
@@ -214,23 +251,70 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
reply->deleteLater(); reply->deleteLater();
auto errorCode = apiUtils::checkNetworkReplyErrors(*sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody); auto decryptionResult =
if (errorCode) { tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish();
return;
}
QSimpleCrypto::QBlockCipher blockCipher; auto processResponse = [promise, encRequestData](const GatewayController::DecryptionResult &decryptionResult,
try { const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
QByteArray responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt); const QString &replyErrorString, int httpStatusCode) {
promise->addResult(qMakePair(ErrorCode::NoError, responseBody)); auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
promise->finish(); decryptionResult.decryptedBody);
} catch (...) { if (errorCode) {
Utils::logException(); promise->addResult(qMakePair(errorCode, QByteArray()));
qCritical() << "error when decrypting the request body"; promise->finish();
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray())); return;
}
if (!decryptionResult.isDecryptionSuccessful) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
return;
}
promise->addResult(qMakePair(ErrorCode::NoError, decryptionResult.decryptedBody));
promise->finish(); promise->finish();
};
if (sslErrors->isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
QStringList baseUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
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");
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
const QString &replyErrorString, int httpStatusCode) {
GatewayController::DecryptionResult result;
result.decryptedBody = decryptedBody;
result.isDecryptionSuccessful = isDecryptionSuccessful;
processResponse(result, sslErrors, replyError, replyErrorString, httpStatusCode);
});
});
});
} else {
processResponse(decryptionResult, *sslErrors, replyError, replyErrorString, httpStatusCode);
} }
}); });
@@ -253,6 +337,9 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
} else { } else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", "); baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
} }
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(baseUrls.begin(), baseUrls.end(), generator);
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
@@ -273,7 +360,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec(QEventLoop::ExcludeUserInputEvents);
if (reply->error() == QNetworkReply::NetworkError::NoError) { if (reply->error() == QNetworkReply::NetworkError::NoError) {
auto encryptedResponseBody = reply->readAll(); auto encryptedResponseBody = reply->readAll();
@@ -323,9 +410,23 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
return {}; return {};
} }
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt) bool isDecryptionSuccessful)
{ {
const QByteArray &responseBody = decryptedResponseBody;
int httpStatus = -1;
if (isDecryptionSuccessful) {
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
httpStatus = jsonObj.value("http_status").toInt(-1);
}
} else {
qDebug() << "failed to decrypt the data";
return true;
}
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) { if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << "timeout occurred"; qDebug() << "timeout occurred";
qDebug() << replyError; qDebug() << replyError;
@@ -333,7 +434,7 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
} else if (responseBody.contains("html")) { } else if (responseBody.contains("html")) {
qDebug() << "the response contains an html tag"; qDebug() << "the response contains an html tag";
return true; return true;
} else if (replyError == QNetworkReply::NetworkError::ContentNotFoundError) { } else if (httpStatus == httpStatusCodeNotFound) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2) if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) { || responseBody.contains(errorResponsePattern3)) {
return false; return false;
@@ -341,24 +442,18 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
qDebug() << replyError; qDebug() << replyError;
return true; return true;
} }
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) { } else if (httpStatus == httpStatusCodeNotImplemented) {
if (responseBody.contains(updateRequestResponsePattern)) { if (responseBody.contains(updateRequestResponsePattern)) {
return false; return false;
} else { } else {
qDebug() << replyError; qDebug() << replyError;
return true; return true;
} }
} else if (httpStatus == httpStatusCodeConflict) {
return false;
} else if (replyError != QNetworkReply::NetworkError::NoError) { } else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError; qDebug() << replyError;
return true; return true;
} else if (checkEncryption) {
try {
QSimpleCrypto::QBlockCipher blockCipher;
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
} catch (...) {
qDebug() << "failed to decrypt the data";
return true;
}
} }
return false; return false;
} }
@@ -385,7 +480,7 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec(QEventLoop::ExcludeUserInputEvents);
auto result = replyProcessingFunction(reply, sslErrors); auto result = replyProcessingFunction(reply, sslErrors);
reply->deleteLater(); reply->deleteLater();
@@ -407,7 +502,7 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit); connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(); wait.exec(QEventLoop::ExcludeUserInputEvents);
if (reply->error() == QNetworkReply::NetworkError::NoError) { if (reply->error() == QNetworkReply::NetworkError::NoError) {
reply->deleteLater(); reply->deleteLater();
@@ -435,3 +530,139 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
} }
} }
} }
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
std::function<void(const QStringList &)> onComplete)
{
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
onComplete({});
return;
}
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);
QNetworkReply *reply = amnApp->networkManager()->get(request);
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray encrypted = reply->readAll();
reply->deleteLater();
QByteArray responseBody;
try {
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray h = hash.result().toHex();
QByteArray decKey = QByteArray::fromHex(h.left(64));
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encrypted);
QSimpleCrypto::QBlockCipher cipher;
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
} else {
responseBody = encrypted;
}
} catch (...) {
Utils::logException();
qCritical() << "error decrypting payload";
QMetaObject::invokeMethod(
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
return;
}
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
QStringList endpoints;
for (const QJsonValue &endpoint : endpointsArray)
endpoints.push_back(endpoint.toString());
QStringList shuffled = endpoints;
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(shuffled.begin(), shuffled.end(), generator);
onComplete(shuffled);
return;
}
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << httpStatusCode;
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
QMetaObject::invokeMethod(
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
});
}
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex,
std::function<void(const QString &)> onComplete)
{
if (currentProxyIndex >= proxyUrls.size()) {
onComplete("");
return;
}
QNetworkRequest request;
request.setTransferTimeout(1000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(proxyUrls[currentProxyIndex] + "lmbd-health");
QNetworkReply *reply = amnApp->networkManager()->get(request);
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) {
// *(state->sslErrors) = e;
// });
connect(reply, &QNetworkReply::finished, this, [this, proxyUrls, currentProxyIndex, onComplete, reply]() {
reply->deleteLater();
if (reply->error() == QNetworkReply::NoError) {
m_proxyUrl = proxyUrls[currentProxyIndex];
onComplete(m_proxyUrl);
return;
}
qDebug() << "go to the next proxy endpoint";
QMetaObject::invokeMethod(this, [=]() { getProxyUrlAsync(proxyUrls, currentProxyIndex + 1, onComplete); }, Qt::QueuedConnection);
});
}
void GatewayController::bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
{
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
if (proxyUrl.isEmpty()) {
onComplete(QByteArray(), false, *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
return;
}
QNetworkRequest request = encRequestData.request;
request.setUrl(endpoint.arg(proxyUrl));
QNetworkReply *reply = amnApp->networkManager()->post(request, encRequestData.requestBody);
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, encRequestData, reply, this]() {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
onComplete(decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful, *sslErrors, replyError, replyErrorString,
httpStatusCode);
});
}
+21 -4
View File
@@ -5,6 +5,8 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QObject> #include <QObject>
#include <QPair> #include <QPair>
#include <QPromise>
#include <QSharedPointer>
#include "core/defs.h" #include "core/defs.h"
@@ -24,7 +26,8 @@ public:
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload); QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
private: private:
struct EncryptedRequestData { struct EncryptedRequestData
{
QNetworkRequest request; QNetworkRequest request;
QByteArray requestBody; QByteArray requestBody;
QByteArray key; QByteArray key;
@@ -33,15 +36,29 @@ private:
amnezia::ErrorCode errorCode; amnezia::ErrorCode errorCode;
}; };
struct DecryptionResult
{
QByteArray decryptedBody;
bool isDecryptionSuccessful;
};
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload); EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
DecryptionResult tryDecryptResponseBody(const QByteArray &encryptedResponseBody, QNetworkReply::NetworkError replyError,
const QByteArray &key, const QByteArray &iv, const QByteArray &salt);
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode); QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption, bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody, bool isDecryptionSuccessful);
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode, void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction, std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction); std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
std::function<void(const QStringList &)> onComplete);
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
void bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
int m_requestTimeoutMsecs; int m_requestTimeoutMsecs;
QString m_gatewayEndpoint; QString m_gatewayEndpoint;
bool m_isDevEnvironment = false; bool m_isDevEnvironment = false;
+25 -7
View File
@@ -345,7 +345,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
return true; return true;
} }
if (container == DockerContainer::Awg) { if (ContainerProps::isAwgContainer(container)) {
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)) != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort) || (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
@@ -367,11 +367,11 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader) || (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)) != newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) || (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) != newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
// || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize) || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)
// != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)) != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
// || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize) || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
// != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)) != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)))
return true; return true;
} }
@@ -419,6 +419,18 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
cbReadStdOut, cbReadStdErr); cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut; qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
return ErrorCode::ServerLinuxKernelTooOld;
}
}
}
if (stdOut.contains("lock")) if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError; return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found")) if (stdOut.contains("command not found"))
@@ -648,6 +660,11 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } }); vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } }); vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
vars.append({ { "$SPECIAL_JUNK_1", amneziaWireguarConfig.value(config_key::specialJunk1).toString() } });
vars.append({ { "$SPECIAL_JUNK_2", amneziaWireguarConfig.value(config_key::specialJunk2).toString() } });
vars.append({ { "$SPECIAL_JUNK_3", amneziaWireguarConfig.value(config_key::specialJunk3).toString() } });
vars.append({ { "$SPECIAL_JUNK_4", amneziaWireguarConfig.value(config_key::specialJunk4).toString() } });
vars.append({ { "$SPECIAL_JUNK_5", amneziaWireguarConfig.value(config_key::specialJunk5).toString() } });
// Socks5 proxy vars // Socks5 proxy vars
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } }); vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
@@ -657,7 +674,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SOCKS5_USER", socks5user } }); vars.append({ { "$SOCKS5_USER", socks5user } });
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } }); vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray) QString serverIp = (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
? NetworkUtilities::getIPAddress(credentials.hostName) ? NetworkUtilities::getIPAddress(credentials.hostName)
: credentials.hostName; : credentials.hostName;
if (!serverIp.isEmpty()) { if (!serverIp.isEmpty()) {
@@ -99,11 +99,12 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString); protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) { if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
// add mtu for old configs // add mtu for old configs
if (vpnConfigData[config_key::mtu].toString().isEmpty()) { if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
vpnConfigData[config_key::mtu] = vpnConfigData[config_key::mtu] =
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
} }
} }
+2
View File
@@ -61,6 +61,7 @@ namespace amnezia
ServerDockerOnCgroupsV2 = 211, ServerDockerOnCgroupsV2 = 211,
ServerCgroupMountpoint = 212, ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213, DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
// Ssh connection errors // Ssh connection errors
SshRequestDeniedError = 300, SshRequestDeniedError = 300,
@@ -121,6 +122,7 @@ namespace amnezia
ApiMigrationError = 1110, ApiMigrationError = 1110,
ApiUpdateRequestError = 1111, ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112, ApiSubscriptionExpiredError = 1112,
ApiPurchaseError = 1113,
// QFile errors // QFile errors
OpenError = 1200, OpenError = 1200,
+2
View File
@@ -29,6 +29,7 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break; case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break; case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break; case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
// Libssh errors // Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -78,6 +79,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); 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::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; 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;
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
// QFile errors // QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
+52 -110
View File
@@ -1,132 +1,74 @@
#include "ipcclient.h" #include "ipcclient.h"
#include "ipc.h"
#include <QRemoteObjectNode> #include <QRemoteObjectNode>
#include <QtNetwork/qlocalsocket.h>
IpcClient *IpcClient::m_instance = nullptr;
IpcClient::IpcClient(QObject *parent) : QObject(parent) IpcClient::IpcClient(QObject *parent) : QObject(parent)
{ {
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
} }
IpcClient::~IpcClient() IpcClient& IpcClient::Instance()
{ {
if (m_localSocket) thread_local IpcClient ipcClient;
m_localSocket->close(); return ipcClient;
}
bool IpcClient::isSocketConnected() const
{
return m_isSocketConnected;
}
IpcClient *IpcClient::Instance()
{
return m_instance;
} }
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface() QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
{ {
if (!Instance()) QSharedPointer<IpcInterfaceReplica> rep = Instance().m_interface;
return nullptr; if (rep.isNull()) {
return Instance()->m_ipcClient; qCritical() << "IpcClient::Interface(): Failed to acquire replica";
}
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
{
if (!Instance())
return nullptr;
return Instance()->m_Tun2SocksClient;
}
bool IpcClient::init(IpcClient *instance)
{
m_instance = instance;
Instance()->m_localSocket = new QLocalSocket(Instance());
connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() {
Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data());
auto cliNode = Instance()->m_ClientNode.acquire<IpcInterfaceReplica>();
cliNode->waitForSource(5000);
Instance()->m_ipcClient.reset(cliNode);
if (!Instance()->m_ipcClient) {
qWarning() << "IpcClient is not ready!";
}
Instance()->m_ipcClient->waitForSource(1000);
if (!Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient replica is not connected!";
}
auto t2sNode = Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>();
t2sNode->waitForSource(5000);
Instance()->m_Tun2SocksClient.reset(t2sNode);
if (!Instance()->m_Tun2SocksClient) {
qWarning() << "IpcClient::m_Tun2SocksClient is not ready!";
}
Instance()->m_Tun2SocksClient->waitForSource(1000);
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
}
});
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
[instance]() { instance->m_isSocketConnected = false; });
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
Instance()->m_localSocket->waitForConnected();
if (!Instance()->m_ipcClient) {
qDebug() << "IpcClient::init failed";
return false;
}
qDebug() << "IpcClient::init succeed";
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
}
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
{
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
return nullptr; return nullptr;
} }
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::Interface(): Replica is invalid";
}
return rep;
}
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess(); QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
futureResult.waitForFinished(5000); {
return withInterface([](QSharedPointer<IpcInterfaceReplica> &iface) -> QSharedPointer<IpcProcessInterfaceReplica> {
auto createPrivilegedProcess = iface->createPrivilegedProcess();
if (!createPrivilegedProcess.waitForFinished()) {
qCritical() << "Failed to create privileged process";
return nullptr;
}
int pid = futureResult.returnValue(); const int pid = createPrivilegedProcess.returnValue();
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor()); auto* node = new QRemoteObjectNode();
Instance()->m_processNodes.insert(pid, pd); node->connectToNode(QUrl(QString("local:%1").arg(amnezia::getIpcProcessUrl(pid))));
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data())); QSharedPointer<IpcProcessInterfaceReplica> rep(
node->acquire<IpcProcessInterfaceReplica>(),
connect(pd->localSocket.data(), &QLocalSocket::connected, pd->replicaNode.data(), [pd]() { [node] (IpcProcessInterfaceReplica *ptr) {
pd->replicaNode->addClientSideConnection(pd->localSocket.data()); delete ptr;
node->deleteLater();
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
pd->ipcProcess.reset(priv);
if (!pd->ipcProcess) {
qWarning() << "Acquire PrivilegedProcess failed";
} else {
pd->ipcProcess->waitForSource(1000);
if (!pd->ipcProcess->isReplicaValid()) {
qWarning() << "PrivilegedProcess replica is not connected!";
} }
);
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(), if (rep.isNull()) {
[pd]() { pd->replicaNode->deleteLater(); }); qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to acquire replica";
return nullptr;
}
if (!rep->waitForSource()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Replica is invalid";
return nullptr;
} }
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected();
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess); return rep;
return processReplica; },
[]() -> QSharedPointer<IpcProcessInterfaceReplica> {
return nullptr;
});
} }
+35 -35
View File
@@ -4,53 +4,53 @@
#include <QLocalSocket> #include <QLocalSocket>
#include <QObject> #include <QObject>
#include "ipc.h"
#include "rep_ipc_interface_replica.h" #include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h" #include "rep_ipc_process_interface_replica.h"
#include "privileged_process.h"
class IpcClient : public QObject class IpcClient : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit IpcClient(QObject *parent = nullptr); explicit IpcClient(QObject *parent = nullptr);
static IpcClient *Instance(); static IpcClient& Instance();
static bool init(IpcClient *instance);
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
bool isSocketConnected() const; static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
template <typename Func>
static auto withInterface(Func func)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
using ReturnType = decltype(func(std::declval<QSharedPointer<IpcInterfaceReplica>>()));
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
qWarning() << "IpcClient::withInterface(): Service is not running";
if constexpr (std::is_void_v<ReturnType>)
return;
else
return ReturnType{};
}
return func(iface);
}
template <typename OnSuccess, typename OnFailure>
static auto withInterface(OnSuccess onSuccess, OnFailure onFailure)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
return onFailure();
}
return onSuccess(iface);
}
signals: signals:
private: private:
~IpcClient() override; QRemoteObjectNode m_node;
QSharedPointer<IpcInterfaceReplica> m_interface;
QRemoteObjectNode m_ClientNode;
QRemoteObjectNode m_Tun2SocksNode;
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
QPointer<QLocalSocket> m_localSocket;
QPointer<QLocalSocket> m_tun2socksSocket;
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
struct ProcessDescriptor {
ProcessDescriptor () {
replicaNode = QSharedPointer<QRemoteObjectNode>(new QRemoteObjectNode());
ipcProcess = QSharedPointer<PrivilegedProcess>();
localSocket = QSharedPointer<QLocalSocket>();
}
QSharedPointer<PrivilegedProcess> ipcProcess;
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
bool m_isSocketConnected {false};
static IpcClient *m_instance;
}; };
#endif // IPCCLIENT_H #endif // IPCCLIENT_H
+38 -16
View File
@@ -1,11 +1,12 @@
#include "networkUtilities.h" #include "networkUtilities.h"
#include <QtNetwork/qnetworkinterface.h>
#include <cstddef>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
#include <Ipexport.h> #include <Ipexport.h>
#include <Ws2tcpip.h> #include <Ws2tcpip.h>
#include <ws2ipdef.h> #include <ws2ipdef.h>
#include <stdint.h>
#include <Iphlpapi.h> #include <Iphlpapi.h>
#include <Iptypes.h> #include <Iptypes.h>
#include <WinSock2.h> #include <WinSock2.h>
@@ -30,6 +31,15 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <net/route.h> #include <net/route.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <net/if.h>
#endif #endif
#include <QHostAddress> #include <QHostAddress>
@@ -170,7 +180,7 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
qDebug() << "Getting Current Internet Adapter that routes to" qDebug() << "Getting Current Internet Adapter that routes to"
<< dst.toString(); << dst.toString();
quint32_be ipBigEndian; quint32 ipBigEndian;
quint32 ip = dst.toIPv4Address(); quint32 ip = dst.toIPv4Address();
qToBigEndian(ip, &ipBigEndian); qToBigEndian(ip, &ipBigEndian);
_MIB_IPFORWARDROW routeInfo; _MIB_IPFORWARDROW routeInfo;
@@ -239,12 +249,14 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
} }
#endif #endif
QString NetworkUtilities::getGatewayAndIface() QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
constexpr int BUFF_LEN = 100; constexpr int BUFF_LEN = 100;
char buff[BUFF_LEN] = {'\0'}; char buff[BUFF_LEN] = {'\0'};
QString result;
QString resGateway;
int resIndex = -1;
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr; PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
DWORD dwRetVal = DWORD dwRetVal =
@@ -252,7 +264,7 @@ QString NetworkUtilities::getGatewayAndIface()
if (dwRetVal != NO_ERROR) { if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed."; qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return ""; return {};
} }
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses; PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
@@ -267,7 +279,9 @@ QString NetworkUtilities::getGatewayAndIface()
struct sockaddr_in addr; struct sockaddr_in addr;
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) { if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
qDebug() << "this is true v4 !"; qDebug() << "this is true v4 !";
result = gw;
resGateway = gw;
resIndex = pCurAddress->IfIndex;
} }
} }
} }
@@ -275,7 +289,7 @@ QString NetworkUtilities::getGatewayAndIface()
} }
free(pAdapterAddresses); free(pAdapterAddresses);
return result; return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100; constexpr int BUFFER_SIZE = 100;
@@ -292,7 +306,7 @@ QString NetworkUtilities::getGatewayAndIface()
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) { if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed"); perror("socket failed");
return ""; return {};
} }
memset(msgbuf, 0, sizeof(msgbuf)); memset(msgbuf, 0, sizeof(msgbuf));
@@ -316,7 +330,7 @@ QString NetworkUtilities::getGatewayAndIface()
/* send msg */ /* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) { if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed"); perror("send failed");
return ""; return {};
} }
/* receive response */ /* receive response */
@@ -325,7 +339,7 @@ QString NetworkUtilities::getGatewayAndIface()
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0); received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) { if (received_bytes < 0) {
perror("Error in recv"); perror("Error in recv");
return ""; return {};
} }
nlh = (struct nlmsghdr *) ptr; nlh = (struct nlmsghdr *) ptr;
@@ -335,7 +349,7 @@ QString NetworkUtilities::getGatewayAndIface()
(nlmsg->nlmsg_type == NLMSG_ERROR)) (nlmsg->nlmsg_type == NLMSG_ERROR))
{ {
perror("Error in received packet"); perror("Error in received packet");
return ""; return {};
} }
/* If we received all data break */ /* If we received all data break */
@@ -388,10 +402,12 @@ QString NetworkUtilities::getGatewayAndIface()
} }
} }
close(sock); close(sock);
return gateway_address; return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
#endif #endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway; QString gateway;
int index = -1;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY}; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6}; int afinet_type[] = {AF_INET, AF_INET6};
@@ -401,17 +417,17 @@ QString NetworkUtilities::getGatewayAndIface()
size_t needed = 0; size_t needed = 0;
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0) if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
return ""; return {};
char* buf; char* buf;
if ((buf = new char[needed]) == 0) if ((buf = new char[needed]) == 0)
return ""; return {};
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0) if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
{ {
qDebug() << "sysctl: net.route.0.0.dump"; qDebug() << "sysctl: net.route.0.0.dump";
delete[] buf; delete[] buf;
return gateway; return {};
} }
struct rt_msghdr* rt; struct rt_msghdr* rt;
@@ -449,7 +465,10 @@ QString NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr, &(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
sizeof(struct in_addr)); sizeof(struct in_addr));
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr) if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
{
gateway = dstStr4; gateway = dstStr4;
index = rt->rtm_index;
}
break; break;
} }
} }
@@ -463,7 +482,10 @@ QString NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr, &(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
sizeof(struct in6_addr)); sizeof(struct in6_addr));
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr) if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
{
gateway = dstStr6; gateway = dstStr6;
index = rt->rtm_index;
}
break; break;
} }
} }
@@ -472,6 +494,6 @@ QString NetworkUtilities::getGatewayAndIface()
free(buf); free(buf);
} }
return gateway; return { gateway, QNetworkInterface::interfaceFromIndex(index) };
#endif #endif
} }
+2 -2
View File
@@ -6,7 +6,7 @@
#include <QString> #include <QString>
#include <QHostAddress> #include <QHostAddress>
#include <QNetworkReply> #include <QNetworkReply>
#include <QtNetwork/qnetworkinterface.h>
class NetworkUtilities : public QObject class NetworkUtilities : public QObject
{ {
@@ -17,7 +17,7 @@ public:
static bool checkIPv4Format(const QString &ip); static bool checkIPv4Format(const QString &ip);
static bool checkIpSubnetFormat(const QString &ip); static bool checkIpSubnetFormat(const QString &ip);
static bool checkIpv6Enabled(); static bool checkIpv6Enabled();
static QString getGatewayAndIface(); static QPair<QString, QNetworkInterface> getGatewayAndIface();
// Returns the Interface Index that could Route to dst // Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst); static int AdapterIndexTo(const QHostAddress& dst);
+194
View File
@@ -0,0 +1,194 @@
#include "osSignalHandler.h"
#include <QCoreApplication>
#include <QMetaObject>
#include <QSocketNotifier>
#include "../amnezia_application.h"
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include <pthread.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <unistd.h>
#elif defined(Q_OS_MACOS)
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#endif
#ifdef Q_OS_WIN
#include <QAbstractNativeEventFilter>
#include <windows.h>
#endif
namespace
{
static bool initialized = false;
#ifdef Q_OS_WIN
class WindowsCloseFilter : public QAbstractNativeEventFilter
{
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override
{
MSG *msg = static_cast<MSG *>(message);
switch (msg->message) {
case WM_CLOSE: {
const HWND active = GetActiveWindow();
const HWND self = msg->hwnd;
if (active != self) {
AmneziaApplication *app = qobject_cast<AmneziaApplication *>(QCoreApplication::instance());
if (app) {
QMetaObject::invokeMethod(app, "forceQuit", Qt::QueuedConnection);
}
}
}
}
return false;
};
};
static WindowsCloseFilter *windowsFilter = nullptr;
#endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
static int signalFd = -1;
static QSocketNotifier *socketNotifier = nullptr;
static void setupUnixSignalHandler()
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
pthread_sigmask(SIG_BLOCK, &set, nullptr);
signalFd = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
if (signalFd < 0)
return;
socketNotifier = new QSocketNotifier(signalFd, QSocketNotifier::Read, QCoreApplication::instance());
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
signalfd_siginfo fdsi;
::read(signalFd, &fdsi, sizeof(fdsi));
if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) {
QCoreApplication::quit();
}
});
}
#elif defined(Q_OS_MACOS)
static int signalPipe[2] = { -1, -1 };
static QSocketNotifier *socketNotifier = nullptr;
static void macSignalHandler(int)
{
if (signalPipe[1] >= 0) {
const char ch = 1;
::write(signalPipe[1], &ch, sizeof(ch));
}
}
static void setupUnixSignalHandler()
{
if (::pipe(signalPipe) != 0)
return;
::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK);
::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK);
socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance());
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
char buf[16];
::read(signalPipe[0], buf, sizeof(buf));
QCoreApplication::quit();
});
struct sigaction sa {};
sa.sa_handler = macSignalHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
}
#endif
static void cleanupUnixSignalHandler()
{
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (socketNotifier) {
socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
}
if (signalFd >= 0) {
::close(signalFd);
signalFd = -1;
}
#elif defined(Q_OS_MACOS)
struct sigaction sa {};
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
if (socketNotifier) {
socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
}
if (signalPipe[0] >= 0) {
::close(signalPipe[0]);
signalPipe[0] = -1;
}
if (signalPipe[1] >= 0) {
::close(signalPipe[1]);
signalPipe[1] = -1;
}
#endif
#ifdef Q_OS_WIN
if (windowsFilter) {
QCoreApplication::instance()->removeNativeEventFilter(windowsFilter);
delete windowsFilter;
windowsFilter = nullptr;
}
#endif
}
}
OsSignalHandler::OsSignalHandler(QObject *parent) : QObject(parent)
{
}
void OsSignalHandler::setup()
{
if (initialized)
return;
initialized = true;
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACOS)
setupUnixSignalHandler();
#endif
#ifdef Q_OS_WIN
windowsFilter = new WindowsCloseFilter();
QCoreApplication::instance()->installNativeEventFilter(windowsFilter);
#endif
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); });
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef OSSIGNALHANDLER_H
#define OSSIGNALHANDLER_H
#include <QObject>
class OsSignalHandler : public QObject
{
Q_OBJECT
public:
static void setup();
private:
explicit OsSignalHandler(QObject *parent = nullptr);
static void handleSignal(int signal);
};
#endif // OSSIGNALHANDLER_H
-27
View File
@@ -1,27 +0,0 @@
#include "privileged_process.h"
PrivilegedProcess::PrivilegedProcess() :
IpcProcessInterfaceReplica()
{
}
PrivilegedProcess::~PrivilegedProcess()
{
qDebug() << "PrivilegedProcess::~PrivilegedProcess()";
}
void PrivilegedProcess::waitForFinished(int msecs)
{
QSharedPointer<QEventLoop> loop(new QEventLoop);
connect(this, &PrivilegedProcess::finished, this, [this, loop](int exitCode, QProcess::ExitStatus exitStatus) mutable{
loop->quit();
loop.clear();
});
QTimer::singleShot(msecs, this, [this, loop]() mutable {
loop->quit();
loop.clear();
});
loop->exec();
}
-24
View File
@@ -1,24 +0,0 @@
#ifndef PRIVILEGED_PROCESS_H
#define PRIVILEGED_PROCESS_H
#include <QObject>
#include "rep_ipc_process_interface_replica.h"
// This class is dangerous - instance of this class casted from base class,
// so it support only functions
// Do not add any members into it
//
class PrivilegedProcess : public IpcProcessInterfaceReplica
{
Q_OBJECT
public:
PrivilegedProcess();
~PrivilegedProcess() override;
void waitForFinished(int msecs);
};
#endif // PRIVILEGED_PROCESS_H
+2 -1
View File
@@ -11,7 +11,8 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
case DockerContainer::WireGuard: return QLatin1String("wireguard"); case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Awg: return QLatin1String("awg"); case DockerContainer::Awg2: return QLatin1String("awg");
case DockerContainer::Awg: return QLatin1String("awg_legacy");
case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::Xray: return QLatin1String("xray"); case DockerContainer::Xray: return QLatin1String("xray");
@@ -21,6 +21,7 @@ namespace amnezia::serialization
namespace vless namespace vless
{ {
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage); QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
const QString Serialize(const VlessServerObject &server, const QString &alias);
} // namespace vless } // namespace vless
namespace ss namespace ss
+19
View File
@@ -42,6 +42,25 @@ struct VMessServerObject
}; };
struct VlessServerObject
{
QString address;
QString id; // UUID
int port;
QString flow = "xtls-rprx-vision";
QString encryption = "none";
QString network = "tcp";
QString security = "reality";
QString serverName; // SNI
QString publicKey;
QString shortId;
QString fingerprint = "chrome";
QString spiderX = "";
JSONSTRUCT_COMPARE(VlessServerObject, address, id, port, flow, encryption)
JSONSTRUCT_REGISTER(VlessServerObject, F(address, id, port, flow, encryption, network, security, serverName, publicKey, shortId, fingerprint, spiderX))
};
namespace transfer namespace transfer
{ {
+61 -1
View File
@@ -252,5 +252,65 @@ QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
root["inbounds"] = QJsonArray { inbound }; root["inbounds"] = QJsonArray { inbound };
return root; return root;
} }
} // namespace amnezia::serialization::vless
const QString Serialize(const VlessServerObject &server, const QString &alias)
{
QUrl url;
// Set basic URL components
url.setScheme("vless");
url.setUserInfo(server.id);
url.setHost(server.address);
url.setPort(server.port);
QUrlQuery query;
if (!server.network.isEmpty() && server.network != "tcp") {
query.addQueryItem("type", server.network);
}
if (!server.encryption.isEmpty()) {
query.addQueryItem("encryption", server.encryption);
}
if (!server.security.isEmpty() && server.security != "none") {
query.addQueryItem("security", server.security);
}
if (!server.flow.isEmpty() && (server.security == "xtls" || server.security == "reality")) {
query.addQueryItem("flow", server.flow);
}
if (!server.serverName.isEmpty()) {
query.addQueryItem("sni", server.serverName);
}
if (server.security == "reality") {
if (!server.fingerprint.isEmpty()) {
query.addQueryItem("fp", server.fingerprint);
}
if (!server.publicKey.isEmpty()) {
query.addQueryItem("pbk", server.publicKey);
}
if (!server.shortId.isEmpty()) {
query.addQueryItem("sid", server.shortId);
}
if (!server.spiderX.isEmpty()) {
query.addQueryItem("spiderX", server.spiderX);
}
}
url.setQuery(query);
if (!alias.isEmpty()) {
url.setFragment(alias);
}
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
}
}
-12
View File
@@ -440,18 +440,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
if (!obj.value("I5").isNull()) { if (!obj.value("I5").isNull()) {
config.m_specialJunk["I5"] = obj.value("I5").toString(); config.m_specialJunk["I5"] = obj.value("I5").toString();
} }
if (!obj.value("J1").isNull()) {
config.m_controlledJunk["J1"] = obj.value("J1").toString();
}
if (!obj.value("J2").isNull()) {
config.m_controlledJunk["J2"] = obj.value("J2").toString();
}
if (!obj.value("J3").isNull()) {
config.m_controlledJunk["J3"] = obj.value("J3").toString();
}
if (!obj.value("Itime").isNull()) {
config.m_specialHandshakeTimeout = obj.value("Itime").toString();
}
return true; return true;
} }
-6
View File
@@ -152,12 +152,6 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
for (const QString& key : m_specialJunk.keys()) { for (const QString& key : m_specialJunk.keys()) {
out << key << " = " << m_specialJunk[key] << "\n"; out << key << " = " << m_specialJunk[key] << "\n";
} }
for (const QString& key : m_controlledJunk.keys()) {
out << key << " = " << m_controlledJunk[key] << "\n";
}
if (!m_specialHandshakeTimeout.isNull()) {
out << "Itime = " << m_specialHandshakeTimeout << "\n";
}
// If any extra config was provided, append it now. // If any extra config was provided, append it now.
for (const QString& key : extra.keys()) { for (const QString& key : extra.keys()) {
+1 -3
View File
@@ -8,7 +8,7 @@
#include <QList> #include <QList>
#include <QMap> #include <QMap>
#include <QString> #include <QString>
#include <QMap>
#include "ipaddress.h" #include "ipaddress.h"
class QJsonObject; class QJsonObject;
@@ -57,8 +57,6 @@ class InterfaceConfig {
QString m_underloadPacketMagicHeader; QString m_underloadPacketMagicHeader;
QString m_transportPacketMagicHeader; QString m_transportPacketMagicHeader;
QMap<QString, QString> m_specialJunk; QMap<QString, QString> m_specialJunk;
QMap<QString, QString> m_controlledJunk;
QString m_specialHandshakeTimeout;
QJsonObject toJson() const; QJsonObject toJson() const;
QString toWgConf( QString toWgConf(
+2
View File
@@ -2,6 +2,7 @@
#include <QTimer> #include <QTimer>
#include "amnezia_application.h" #include "amnezia_application.h"
#include "core/osSignalHandler.h"
#include "migrations.h" #include "migrations.h"
#include "version.h" #include "version.h"
@@ -44,6 +45,7 @@ int main(int argc, char *argv[])
#endif #endif
AmneziaApplication app(argc, argv); AmneziaApplication app(argc, argv);
OsSignalHandler::setup();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) { if (isAnotherInstanceRunning()) {
+26 -32
View File
@@ -5,6 +5,9 @@
#include <stdint.h> #include <stdint.h>
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QHostAddress> #include <QHostAddress>
@@ -12,12 +15,13 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue> #include <QJsonValue>
#include <QLocalSocket>
#include <QObject>
#include <QStandardPaths> #include <QStandardPaths>
#include <QTimer>
#include "ipaddress.h"
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
#include "models/server.h"
#include "daemon/daemonerrors.h" #include "daemon/daemonerrors.h"
#include "protocols/protocols_defs.h" #include "protocols/protocols_defs.h"
@@ -115,7 +119,6 @@ void LocalSocketController::daemonConnected() {
} }
void LocalSocketController::activate(const QJsonObject &rawConfig) { void LocalSocketController::activate(const QJsonObject &rawConfig) {
QString protocolName = rawConfig.value("protocol").toString(); QString protocolName = rawConfig.value("protocol").toString();
int splitTunnelType = rawConfig.value("splitTunnelType").toInt(); int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
@@ -132,13 +135,16 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex)); // json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key)); json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip)); json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
m_deviceIpv4 = wgConfig.value(amnezia::config_key::client_ip).toString();
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable. // set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4. // this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
// Otherwise some OSes (Linux) try IPv6 forever and hang. // Otherwise some OSes (Linux) try IPv6 forever and hang.
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193) // https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
// https://man7.org/linux/man-pages/man5/gai.conf.5.html // https://man7.org/linux/man-pages/man5/gai.conf.5.html
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
// simply "dead::1" is globally-routable, don't use it
json.insert("deviceIpv6Address", "fd58:baa6:dead::1");
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key)); json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key)); json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
@@ -220,7 +226,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses); json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
QJsonArray jsExcludedAddresses; QJsonArray jsExcludedAddresses;
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName)); jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
if (splitTunnelType == 2) { if (splitTunnelType == 2) {
@@ -255,50 +260,33 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); 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::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); 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));
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined() } else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined() && !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined() && !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
// && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
// && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).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()
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()
&& !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()*/) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); 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::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); 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::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); 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::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
// json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize)); 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::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); 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::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); 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::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
// json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2)); 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::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
// json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); 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::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); write(json);
@@ -449,6 +437,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
} }
if (type == "status") { if (type == "status") {
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway"); QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
if (!serverIpv4Gateway.isString()) { if (!serverIpv4Gateway.isString()) {
logger.error() << "Unexpected serverIpv4Gateway value"; logger.error() << "Unexpected serverIpv4Gateway value";
@@ -493,6 +482,11 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
logger.debug() << "Handshake completed with:" logger.debug() << "Handshake completed with:"
<< pubkey.toString(); << pubkey.toString();
checkStatus();
emit statusUpdated("", m_deviceIpv4, 0, 0);
emit connected(pubkey.toString()); emit connected(pubkey.toString());
return; return;
} }
+2
View File
@@ -12,6 +12,7 @@
#include "controllerimpl.h" #include "controllerimpl.h"
class QJsonObject; class QJsonObject;
class LocalSocketController final : public ControllerImpl { class LocalSocketController final : public ControllerImpl {
@@ -58,6 +59,7 @@ class LocalSocketController final : public ControllerImpl {
QByteArray m_buffer; QByteArray m_buffer;
QString m_deviceIpv4;
std::function<void(const QString&)> m_logCallback = nullptr; std::function<void(const QString&)> m_logCallback = nullptr;
QTimer m_initializingTimer; QTimer m_initializingTimer;
+18 -39
View File
@@ -11,7 +11,6 @@
#include "logger.h" #include "logger.h"
//#include "mozillavpn.h" //#include "mozillavpn.h"
#include "networkwatcherimpl.h" #include "networkwatcherimpl.h"
#include "platforms/dummy/dummynetworkwatcher.h"
//#include "settingsholder.h" //#include "settingsholder.h"
#ifdef MZ_WINDOWS #ifdef MZ_WINDOWS
@@ -51,7 +50,7 @@ NetworkWatcher::NetworkWatcher() { MZ_COUNT_CTOR(NetworkWatcher); }
NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); } NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); }
void NetworkWatcher::initialize() { void NetworkWatcher::initialize() {
logger.debug() << "Initialize"; logger.debug() << "Initialize NetworkWatcher";
#if defined(MZ_WINDOWS) #if defined(MZ_WINDOWS)
m_impl = new WindowsNetworkWatcher(this); m_impl = new WindowsNetworkWatcher(this);
@@ -69,59 +68,39 @@ void NetworkWatcher::initialize() {
m_impl = new DummyNetworkWatcher(this); m_impl = new DummyNetworkWatcher(this);
#endif #endif
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this, connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
&NetworkWatcher::unsecuredNetwork); &NetworkWatcher::unsecuredNetwork);
connect(m_impl, &NetworkWatcherImpl::networkChanged, this, connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
&NetworkWatcher::networkChange); &NetworkWatcher::networkChanged);
connect(m_impl, &NetworkWatcherImpl::wakeup, this,
&NetworkWatcher::wakeup);
m_impl->initialize(); m_impl->initialize();
// Enable sleep/wake monitoring for VPN auto-reconnection
// TODO: IMPL FOR AMNEZIA logger.debug() << "Starting NetworkWatcher for sleep/wake monitoring";
#if 0 logger.debug() << "About to call m_impl->start()";
SettingsHolder* settingsHolder = SettingsHolder::instance(); try {
Q_ASSERT(settingsHolder);
m_active = settingsHolder->unsecuredNetworkAlert() ||
settingsHolder->captivePortalAlert();
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
if (m_active) {
m_impl->start(); m_impl->start();
logger.debug() << "m_impl->start() completed successfully";
} catch (const std::exception& e) {
logger.error() << "Exception in m_impl->start():" << e.what();
} catch (...) {
logger.error() << "Unknown exception in m_impl->start()";
} }
m_active = true;
connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this, m_reportUnsecuredNetwork = false; // Disable unsecured network alerts for Amnezia
&NetworkWatcher::settingsChanged);
connect(settingsHolder, &SettingsHolder::captivePortalAlertChanged, this,
&NetworkWatcher::settingsChanged);
#endif
} }
void NetworkWatcher::settingsChanged() { void NetworkWatcher::settingsChanged() {
// TODO: IMPL FOR AMNEZIA // For Amnezia: Keep NetworkWatcher always active for sleep/wake monitoring
#if 0 logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
SettingsHolder* settingsHolder = SettingsHolder::instance();
m_active = settingsHolder->unsecuredNetworkAlert() ||
settingsHolder->captivePortalAlert();
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
if (m_active) {
logger.debug()
<< "Starting Network Watcher; Reporting of Unsecured Networks: "
<< m_reportUnsecuredNetwork;
m_impl->start();
} else {
logger.debug() << "Stopping Network Watcher";
m_impl->stop();
}
#endif
} }
void NetworkWatcher::unsecuredNetwork(const QString& networkName, void NetworkWatcher::unsecuredNetwork(const QString& networkName,
const QString& networkId) { const QString& networkId) {
logger.debug() << "Unsecured network:" << logger.sensitive(networkName) logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
<< "id:" << logger.sensitive(networkId); << "id:" << logger.sensitive(networkId);
#ifndef UNIT_TEST #ifndef UNIT_TEST
if (!m_reportUnsecuredNetwork) { if (!m_reportUnsecuredNetwork) {
logger.debug() << "Disabled. Ignoring unsecured network"; logger.debug() << "Disabled. Ignoring unsecured network";
+2 -1
View File
@@ -32,7 +32,8 @@ public:
QNetworkInformation::Reachability getReachability(); QNetworkInformation::Reachability getReachability();
signals: signals:
void networkChange(); void networkChanged();
void wakeup();
private: private:
void settingsChanged(); void settingsChanged();
+2
View File
@@ -41,6 +41,8 @@ signals:
// TODO: Only windows-networkwatcher has this, the other plattforms should // TODO: Only windows-networkwatcher has this, the other plattforms should
// too. // too.
void networkChanged(QString newBSSID); void networkChanged(QString newBSSID);
void wakeup();
private: private:
bool m_active = false; bool m_active = false;
+5 -2
View File
@@ -41,6 +41,7 @@ void PingHelper::start(const QString& serverIpv4Gateway,
m_gateway = QHostAddress(serverIpv4Gateway); m_gateway = QHostAddress(serverIpv4Gateway);
m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0)); m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0));
m_pingSender = PingSenderFactory::create(m_source, this); m_pingSender = PingSenderFactory::create(m_source, this);
// Some platforms require root access to send and receive ICMP pings. If // Some platforms require root access to send and receive ICMP pings. If
@@ -53,8 +54,10 @@ void PingHelper::start(const QString& serverIpv4Gateway,
connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived, connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived,
Qt::QueuedConnection); Qt::QueuedConnection);
connect(m_pingSender, &PingSender::criticalPingError, this, connect(m_pingSender, &PingSender::criticalPingError, this, [this]() {
[]() { logger.info() << "Encountered Unrecoverable ping error"; }); logger.info() << "Encountered Unrecoverable ping error";
emit connectionLose();
});
// Reset the ping statistics // Reset the ping statistics
m_sequence = 0; m_sequence = 0;
+2
View File
@@ -33,6 +33,8 @@ class PingHelper final : public QObject {
signals: signals:
void pingSentAndReceived(qint64 msec); void pingSentAndReceived(qint64 msec);
void connectionLose();
private: private:
void nextPing(); void nextPing();
+10 -11
View File
@@ -5,27 +5,26 @@
#include "pingsenderfactory.h" #include "pingsenderfactory.h"
#if defined(MZ_LINUX) || defined(MZ_ANDROID) #if defined(MZ_LINUX) || defined(MZ_ANDROID)
//# include "platforms/linux/linuxpingsender.h" # include "platforms/linux/linuxpingsender.h"
#elif defined(MZ_MACOS) || defined(MZ_IOS) #elif defined(MZ_MACOS) || defined(MZ_IOS)
# include "platforms/macos/macospingsender.h" # include "platforms/macos/macospingsender.h"
#elif defined(MZ_WINDOWS) #elif defined(MZ_WINDOWS)
# include "platforms/windows/windowspingsender.h" # include "platforms/windows/windowspingsender.h"
#elif defined(MZ_DUMMY) || defined(UNIT_TEST) #elif defined(MZ_WASM) || defined(UNIT_TEST)
# include "platforms/dummy/dummypingsender.h" # include "platforms/dummy/dummypingsender.h"
#else #else
# error "Unsupported platform" # error "Unsupported platform"
#endif #endif
PingSender* PingSenderFactory::create(const QHostAddress& source, PingSender* PingSenderFactory::create(const QHostAddress& source,
QObject* parent) { QObject* parent) {
#if defined(MZ_LINUX) || defined(MZ_ANDROID) #if defined(MZ_LINUX) || defined(MZ_ANDROID)
return nullptr; return new LinuxPingSender(source, parent);
// return new LinuxPingSender(source, parent);
#elif defined(MZ_MACOS) || defined(MZ_IOS) #elif defined(MZ_MACOS) || defined(MZ_IOS)
return new MacOSPingSender(source, parent); return new MacOSPingSender(source, parent);
#elif defined(MZ_WINDOWS) #elif defined(MZ_WINDOWS)
return new WindowsPingSender(source, parent); return new WindowsPingSender(source, parent);
#else #else
return new DummyPingSender(source, parent); return new DummyPingSender(source, parent);
#endif #endif
} }
+4 -3
View File
@@ -10,9 +10,10 @@ class QHostAddress;
class QObject; class QObject;
class PingSenderFactory final { class PingSenderFactory final {
public: public:
PingSenderFactory() = delete; PingSenderFactory() = delete;
static PingSender* create(const QHostAddress& source, QObject* parent); static PingSender* create(const QHostAddress& source, QObject* parent);
}; };
#endif // PINGSENDERFACTORY_H #endif // PINGSENDERFACTORY_H
@@ -99,7 +99,9 @@ bool AndroidController::initialize()
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)}, {"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)}, {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)}, {"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)} {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
}; };
QJniEnvironment env; QJniEnvironment env;
@@ -536,3 +538,23 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data)); return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
} }
// static
void AndroidController::onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
qDebug() << "Android IME insets changed: height =" << heightDp << "dp";
emit AndroidController::instance()->imeInsetsChanged(heightDp);
}
// static
void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
qDebug() << "Android system bars insets changed: nav bar =" << navBarHeightDp << "dp, status bar =" << statusBarHeightDp << "dp";
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
}
@@ -73,6 +73,8 @@ signals:
void importConfigFromOutside(QString config); void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state); void initConnectionState(Vpn::ConnectionState state);
void authenticationResult(bool result); void authenticationResult(bool result);
void imeInsetsChanged(int heightDp);
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
private: private:
bool isWaitingStatus = true; bool isWaitingStatus = true;
@@ -101,6 +103,8 @@ private:
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri); static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result); static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args); static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
@@ -131,7 +131,7 @@ extension PacketTunnelProvider {
} }
startHandler = completionHandler startHandler = completionHandler
ovpnAdapter?.connect(using: packetFlow) ovpnAdapter?.connect(using: openVPNPacketFlow())
} }
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
@@ -153,7 +153,7 @@ extension PacketTunnelProvider {
} }
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)") ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
stopHandler = completionHandler stopHandler = completionHandler
if vpnReachability.isTracking { if vpnReachability.isTracking {
@@ -293,5 +293,3 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
ovpnLog(.info, message: logMessage) ovpnLog(.info, message: logMessage)
} }
} }
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
@@ -94,15 +94,24 @@ extension PacketTunnelProvider {
} }
} catch { } catch {
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)") wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
completionHandler(nil) errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
return return
} }
} }
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return } guard let completionHandler = completionHandler else { return }
wgAdapter?.getRuntimeConfiguration { settings in guard let wgAdapter = wgAdapter else {
let components = settings!.components(separatedBy: "\n") completionHandler(nil)
return
}
wgAdapter.getRuntimeConfiguration { settings in
guard let settings = settings else {
completionHandler(nil)
return
}
let components = settings.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:] var settingsDictionary: [String: String] = [:]
for component in components { for component in components {
@@ -131,7 +140,7 @@ extension PacketTunnelProvider {
} }
} }
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return } guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 { if messageData.count == 1 && messageData[0] == 0 {
wgAdapter?.getRuntimeConfiguration { settings in wgAdapter?.getRuntimeConfiguration { settings in
@@ -176,7 +185,7 @@ extension PacketTunnelProvider {
} }
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)") wg_log(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
wgAdapter?.stop { error in wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile() ErrorNotifier.removeLastErrorFile()
@@ -1,6 +1,5 @@
import Foundation import Foundation
import NetworkExtension import NetworkExtension
import WireGuardKitGo
enum XrayErrors: Error { enum XrayErrors: Error {
case noXrayConfig case noXrayConfig
@@ -108,6 +107,8 @@ extension PacketTunnelProvider {
return return
} }
self?.updateActiveInterfaceIndexForCurrentPath()
// Launch xray // Launch xray
self?.setupAndStartXray(configData: updatedData) { xrayError in self?.setupAndStartXray(configData: updatedData) { xrayError in
if let xrayError { if let xrayError {
@@ -134,6 +135,15 @@ extension PacketTunnelProvider {
completionHandler() completionHandler()
} }
func sockCallback(fd: uintptr_t) {
if activeIfaceIdx != 0 {
withUnsafePointer(to: activeIfaceIdx) { ptr in
setsockopt(Int32(fd), IPPROTO_IP, IP_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
setsockopt(Int32(fd), IPPROTO_IPV6, IPV6_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
}
}
}
private func setupAndStartXray(configData: Data, private func setupAndStartXray(configData: Data,
completionHandler: @escaping (Error?) -> Void) { completionHandler: @escaping (Error?) -> Void) {
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
@@ -143,6 +153,17 @@ extension PacketTunnelProvider {
return return
} }
updateActiveInterfaceIndexForCurrentPath()
let ctx = Unmanaged.passUnretained(self).toOpaque()
let cb: libxray_sockcallback = { (fd, ctx) in
guard let ctx = ctx else { return }
let instance = Unmanaged<PacketTunnelProvider>.fromOpaque(ctx).takeUnretainedValue()
instance.sockCallback(fd: fd)
}
LibXraySetSockCallback(cb, ctx)
LibXrayRunXray(nil, LibXrayRunXray(nil,
path, path,
Int64.max) Int64.max)
+167 -15
View File
@@ -1,5 +1,6 @@
import Foundation import Foundation
import NetworkExtension import NetworkExtension
import Network
import os import os
import Darwin import Darwin
import OpenVPNAdapter import OpenVPNAdapter
@@ -38,6 +39,12 @@ struct Constants {
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
var wgAdapter: WireGuardAdapter? var wgAdapter: WireGuardAdapter?
var ovpnAdapter: OpenVPNAdapter? var ovpnAdapter: OpenVPNAdapter?
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
private let pathMonitor = NWPathMonitor()
private var didReceiveInitialPathUpdate = false
private var currentPath: Network.NWPath?
private var currentPathSignature: String?
var splitTunnelType: Int? var splitTunnelType: Int?
var splitTunnelSites: [String]? var splitTunnelSites: [String]?
@@ -47,8 +54,90 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)? var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)? var stopHandler: (() -> Void)?
var protoType: TunnelProtoType? var protoType: TunnelProtoType?
var activeIfaceIdx: UInt32 = 0
func openVPNPacketFlow() -> OpenVPNAdapterPacketFlow {
openVPNPacketFlowAdapter
}
override init() {
super.init()
pathMonitor.pathUpdateHandler = { [weak self] path in
guard let self else { return }
self.currentPath = path
let signature = self.pathSignature(for: path)
let hasMeaningfulChange = self.currentPathSignature != signature
self.currentPathSignature = signature
self.updateActiveInterfaceIndex(for: path)
guard self.didReceiveInitialPathUpdate else {
self.didReceiveInitialPathUpdate = true
return
}
guard hasMeaningfulChange, let proto = self.protoType else { return }
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
if proto == .wireguard {
return
}
DispatchQueue.main.async {
self.handle(networkChange: path) { _ in }
}
}
pathMonitor.start(queue: pathMonitorQueue)
currentPath = pathMonitor.currentPath
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
}
func updateActiveInterfaceIndex(for path: Network.NWPath?) {
guard let path else {
activeIfaceIdx = 0
return
}
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .other]
let nonLoopbackInterfaces = path.availableInterfaces.filter { $0.type != .loopback }
let activeInterfaces = nonLoopbackInterfaces.filter { path.usesInterfaceType($0.type) }
let candidate = preferredTypes.compactMap { type in
activeInterfaces.first { $0.type == type }
}.first ?? activeInterfaces.first ?? nonLoopbackInterfaces.first
if let candidate {
activeIfaceIdx = UInt32(candidate.index)
} else {
activeIfaceIdx = 0
}
}
func updateActiveInterfaceIndexForCurrentPath() {
if let currentPath {
currentPathSignature = pathSignature(for: currentPath)
updateActiveInterfaceIndex(for: currentPath)
return
}
currentPath = pathMonitor.currentPath
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
updateActiveInterfaceIndex(for: pathMonitor.currentPath)
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
if messageData.count == 1 && messageData[0] == 0 {
guard let completionHandler else { return }
if protoType == .wireguard {
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
} else {
completionHandler(nil)
}
return
}
guard let message = String(data: messageData, encoding: .utf8) else { guard let message = String(data: messageData, encoding: .utf8) else {
if let completionHandler { if let completionHandler {
completionHandler(nil) completionHandler(nil)
@@ -59,6 +148,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
neLog(.info, title: "App said: ", message: message) neLog(.info, title: "App said: ", message: message)
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else { guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
if protoType == .wireguard {
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
return
}
neLog(.error, message: "Failed to serialize message from app") neLog(.error, message: "Failed to serialize message from app")
return return
} }
@@ -104,6 +197,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return return
} }
didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath()
switch protoType { switch protoType {
case .wireguard: case .wireguard:
startWireguard(activationAttemptId: activationAttemptId, startWireguard(activationAttemptId: activationAttemptId,
@@ -157,28 +253,63 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
of object: Any?, of object: Any?,
change: [NSKeyValueChangeKey: Any]?, change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) { context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey != keyPath else { return } guard Constants.kDefaultPathKey == keyPath else {
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
// comparing the paths' string description, which includes properties not exposed by the class
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
let defPath = defaultPath,
lastPath != defPath || lastPath.description != defPath.description else {
return return
} }
DispatchQueue.main.async { [weak self] in
guard let self, self.defaultPath != nil else { return }
self.handle(networkChange: self.defaultPath!) { _ in }
}
} }
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) { private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
updateActiveInterfaceIndex(for: changePath)
wg_log(.info, message: "Tunnel restarted.") wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion) startTunnel(options: nil, completionHandler: completion)
} }
} }
private extension PacketTunnelProvider {
func pathSignature(for path: Network.NWPath) -> String {
var signatureComponents = [String(describing: path.status)]
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
signatureComponents.append(path.isConstrained ? "con" : "nocon")
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
if lhs.type == rhs.type {
return lhs.index < rhs.index
}
let lhsOrder = preferredTypes.firstIndex(of: lhs.type) ?? preferredTypes.count
let rhsOrder = preferredTypes.firstIndex(of: rhs.type) ?? preferredTypes.count
if lhsOrder == rhsOrder {
return lhs.index < rhs.index
}
return lhsOrder < rhsOrder
}
for interface in sortedInterfaces {
let typeName: String
switch interface.type {
case .wiredEthernet: typeName = "ethernet"
case .wifi: typeName = "wifi"
case .cellular: typeName = "cellular"
case .loopback: typeName = "loopback"
case .other: typeName = "other"
@unknown default: typeName = "unknown"
}
signatureComponents.append("\(typeName):\(interface.index)")
}
// Include currently used interface preference ordering
for type in preferredTypes {
let usesType = path.usesInterfaceType(type)
signatureComponents.append("uses-\(type):\(usesType)")
}
return signatureComponents.joined(separator: "|")
}
}
extension WireGuardLogLevel { extension WireGuardLogLevel {
var osLogLevel: OSLogType { var osLogLevel: OSLogType {
switch self { switch self {
@@ -190,8 +321,27 @@ extension WireGuardLogLevel {
} }
} }
extension NEProviderStopReason: CustomStringConvertible { final class PacketTunnelFlowAdapter: NSObject, OpenVPNAdapterPacketFlow {
public var description: String { private let flow: NEPacketTunnelFlow
init(flow: NEPacketTunnelFlow) {
self.flow = flow
super.init()
}
@objc(readPacketsWithCompletionHandler:)
func readPackets(completionHandler: @escaping ([Data], [NSNumber]) -> Void) {
flow.readPackets(completionHandler: completionHandler)
}
@objc(writePackets:withProtocols:)
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
flow.writePackets(packets, withProtocols: protocols)
}
}
extension NEProviderStopReason {
var amneziaDescription: String {
switch self { switch self {
case .none: case .none:
return "No specific reason" return "No specific reason"
@@ -223,6 +373,8 @@ extension NEProviderStopReason: CustomStringConvertible {
return "The current console user changed" return "The current console user changed"
case .connectionFailed: case .connectionFailed:
return "The connection failed" return "The connection failed"
case .internalError:
return "The network extension reported an internal error"
case .sleep: case .sleep:
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep" return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
case .appUpdate: case .appUpdate:
+40 -7
View File
@@ -11,13 +11,7 @@ class ScreenProtection {
import UIKit import UIKit
public func toggleScreenshots(_ isEnabled: Bool) { public func toggleScreenshots(_ isEnabled: Bool) {
let window = UIApplication.shared.keyWindows.first! ScreenProtection.shared.setScreenshotsEnabled(isEnabled)
if isEnabled {
ScreenProtection.shared.disable(for: window.rootViewController!.view)
} else {
ScreenProtection.shared.enable(for: window.rootViewController!.view)
}
} }
extension UIApplication { extension UIApplication {
@@ -45,6 +39,45 @@ class ScreenProtection {
private var blurView: UIVisualEffectView? private var blurView: UIVisualEffectView?
private var recordingObservation: NSKeyValueObservation? private var recordingObservation: NSKeyValueObservation?
private var desiredScreenshotsEnabled: Bool?
private var retryCount = 0
private var retryWorkItem: DispatchWorkItem?
public func setScreenshotsEnabled(_ isEnabled: Bool) {
DispatchQueue.main.async {
self.desiredScreenshotsEnabled = isEnabled
self.applyScreenshotsSettingOrRetry()
}
}
private func applyScreenshotsSettingOrRetry() {
assert(Thread.isMainThread)
guard let desiredScreenshotsEnabled else { return }
guard let window = UIApplication.shared.keyWindows.first,
let rootView = window.rootViewController?.view else {
retryCount += 1
guard retryCount <= 50 else { return } // ~5s total
retryWorkItem?.cancel()
let item = DispatchWorkItem { [weak self] in
self?.applyScreenshotsSettingOrRetry()
}
retryWorkItem = item
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: item)
return
}
retryWorkItem?.cancel()
retryWorkItem = nil
retryCount = 0
if desiredScreenshotsEnabled {
disable(for: rootView)
} else {
enable(for: rootView)
}
}
public func enable(for view: UIView) { public func enable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+39
View File
@@ -0,0 +1,39 @@
/* 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/. */
#ifndef STOREKITCONTROLLER_H
#define STOREKITCONTROLLER_H
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
@class Product;
@class Transaction;
@class VerificationResult;
API_AVAILABLE(ios(15.0), macos(12.0))
@interface StoreKitController : NSObject
+ (instancetype)sharedInstance;
- (void)purchaseProduct:(NSString *)productIdentifier
completion:(void (^)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error))completion;
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error))completion;
// Fetch product information for a set of identifiers without initiating a purchase
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
completion:(void (^)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error))completion;
@end
#endif // STOREKITCONTROLLER_H
+264
View File
@@ -0,0 +1,264 @@
/* 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/. */
#import "StoreKitController.h"
#import <StoreKit/StoreKit.h>
#include <QtCore/QDebug>
#include <QtCore/QString>
API_AVAILABLE(ios(15.0), macos(12.0))
@interface StoreKitController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
@property (nonatomic, copy) void (^purchaseCompletion)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error);
@property (nonatomic, copy) void (^restoreCompletion)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error);
@property (nonatomic, copy) void (^productsFetchCompletion)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error);
@property (nonatomic, strong) SKProductsRequest *productsRequest;
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *restoredTransactions;
@end
@implementation StoreKitController
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static StoreKitController *instance;
dispatch_once(&onceToken, ^{
if (@available(iOS 15.0, macOS 12.0, *)) {
instance = [[StoreKitController alloc] init];
}
});
return instance;
}
- (instancetype)init API_AVAILABLE(ios(15.0), macos(12.0))
{
self = [super init];
if (self) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)dealloc
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
- (void)purchaseProduct:(NSString *)productIdentifier
completion:(void (^)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.purchaseCompletion = completion;
qInfo().noquote() << "[IAP][StoreKit] Starting purchase for" << QString::fromUtf8(productIdentifier.UTF8String);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performPurchaseAsync:productIdentifier];
});
}
- (void)performPurchaseAsync:(NSString *)productIdentifier API_AVAILABLE(ios(15.0), macos(12.0))
{
dispatch_async(dispatch_get_main_queue(), ^{
@try {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productIdentifier]];
request.delegate = self;
[request start];
} @catch (NSException *exception) {
NSError *error = [NSError errorWithDomain:@"StoreKitController"
code:1
userInfo:@{ NSLocalizedDescriptionKey : exception.reason ?: @"Purchase failed" }];
if (self.purchaseCompletion) {
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
}
}
});
}
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.restoreCompletion = completion;
self.restoredTransactions = [NSMutableArray array];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
completion:(void (^)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.productsFetchCompletion = completion;
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
self.productsRequest.delegate = self;
[self.productsRequest start];
}
#pragma mark - SKProductsRequestDelegate / SKRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if (self.purchaseCompletion) {
SKProduct *product = response.products.firstObject;
if (!product) {
NSError *error = [NSError errorWithDomain:@"StoreKitController"
code:0
userInfo:@{ NSLocalizedDescriptionKey : @"Product not found" }];
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
self.productsRequest = nil;
return;
}
NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
NSString *priceString = [product.price stringValue] ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Received product" << QString::fromUtf8(product.productIdentifier.UTF8String)
<< "price=" << QString::fromUtf8(priceString.UTF8String)
<< "currency=" << QString::fromUtf8(currencyCode.UTF8String);
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
self.productsRequest = nil;
return;
}
if (self.productsFetchCompletion) {
NSMutableArray<NSDictionary *> *productDicts = [NSMutableArray array];
for (SKProduct *p in response.products) {
NSDictionary *productDict = @{
@"productId": p.productIdentifier,
@"title": p.localizedTitle,
@"description": p.localizedDescription,
@"price": p.price.stringValue,
@"currencyCode": [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @""
};
[productDicts addObject:productDict];
NSString *productCurrency = [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
NSString *productPrice = [p.price stringValue] ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Fetched product info" << QString::fromUtf8(p.productIdentifier.UTF8String)
<< "price=" << QString::fromUtf8(productPrice.UTF8String)
<< "currency=" << QString::fromUtf8(productCurrency.UTF8String);
}
self.productsFetchCompletion(productDicts, response.invalidProductIdentifiers, nil);
self.productsFetchCompletion = nil;
self.productsRequest = nil;
return;
}
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
if (self.purchaseCompletion) {
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
}
if (self.productsFetchCompletion) {
self.productsFetchCompletion(@[], @[], error);
self.productsFetchCompletion = nil;
}
self.productsRequest = nil;
}
#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transaction.transactionIdentifier;
qInfo().noquote() << "[IAP][StoreKit] Transaction purchased" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
<< "original=" << QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String);
if (self.purchaseCompletion) {
self.purchaseCompletion(YES,
transaction.transactionIdentifier,
transaction.payment.productIdentifier,
originalTransactionId,
nil);
self.purchaseCompletion = nil;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateFailed:
qInfo().noquote() << "[IAP][StoreKit] Transaction failed" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String)
<< "error=" << QString::fromUtf8(transaction.error.localizedDescription.UTF8String);
if (self.purchaseCompletion) {
self.purchaseCompletion(NO,
transaction.transactionIdentifier,
transaction.payment.productIdentifier,
nil,
transaction.error);
self.purchaseCompletion = nil;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: {
if (self.restoreCompletion) {
NSString *transactionId = transaction.transactionIdentifier ?: @"";
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transactionId;
NSString *productId = transaction.payment.productIdentifier ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Transaction restored"
<< QString::fromUtf8(transactionId.UTF8String)
<< "original="
<< QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
<< "product="
<< QString::fromUtf8((productId ?: @"").UTF8String);
NSDictionary *info = @{
@"transactionId": transactionId,
@"originalTransactionId": originalTransactionId ?: @"",
@"productId": productId ?: @""
};
if (!self.restoredTransactions) {
self.restoredTransactions = [NSMutableArray array];
}
[self.restoredTransactions addObject:info];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStatePurchasing:
case SKPaymentTransactionStateDeferred:
break;
}
}
}
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
if (self.restoreCompletion) {
NSArray<NSDictionary *> *transactions = [self.restoredTransactions copy];
self.restoreCompletion(YES, transactions, nil);
self.restoreCompletion = nil;
self.restoredTransactions = nil;
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
if (self.restoreCompletion) {
self.restoreCompletion(NO, nil, error);
self.restoreCompletion = nil;
self.restoredTransactions = nil;
}
}
@end
+40 -38
View File
@@ -6,8 +6,6 @@ struct WGConfig: Decodable {
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String? let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String? let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String?
let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String? let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String?
let controlledJunk1, controlledJunk2, controlledJunk3: String?
let specialHandshakeTimeout: String?
let dns1: String let dns1: String
let dns2: String let dns2: String
let mtu: String let mtu: String
@@ -28,8 +26,6 @@ struct WGConfig: Decodable {
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax" case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4" case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4"
case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5" case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5"
case controlledJunk1 = "J1", controlledJunk2 = "J2", controlledJunk3 = "J3"
case specialHandshakeTimeout = "Itime"
case dns1 case dns1
case dns2 case dns2
case mtu case mtu
@@ -46,58 +42,64 @@ struct WGConfig: Decodable {
} }
var settings: String { var settings: String {
guard junkPacketCount != nil else { return "" } func trimmed(_ value: String?) -> String? {
guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines),
!value.isEmpty else {
return nil
}
return value
}
guard
let junkPacketCount = trimmed(junkPacketCount),
let junkPacketMinSize = trimmed(junkPacketMinSize),
let junkPacketMaxSize = trimmed(junkPacketMaxSize),
let initPacketJunkSize = trimmed(initPacketJunkSize),
let responsePacketJunkSize = trimmed(responsePacketJunkSize),
let initPacketMagicHeader = trimmed(initPacketMagicHeader),
let responsePacketMagicHeader = trimmed(responsePacketMagicHeader),
let underloadPacketMagicHeader = trimmed(underloadPacketMagicHeader),
let transportPacketMagicHeader = trimmed(transportPacketMagicHeader)
else { return "" }
var settingsLines: [String] = [] var settingsLines: [String] = []
// Required parameters when junkPacketCount is present // Required parameters when junkPacketCount is present
settingsLines.append("Jc = \(junkPacketCount!)") settingsLines.append("Jc = \(junkPacketCount)")
settingsLines.append("Jmin = \(junkPacketMinSize!)") settingsLines.append("Jmin = \(junkPacketMinSize)")
settingsLines.append("Jmax = \(junkPacketMaxSize!)") settingsLines.append("Jmax = \(junkPacketMaxSize)")
settingsLines.append("S1 = \(initPacketJunkSize!)") settingsLines.append("S1 = \(initPacketJunkSize)")
settingsLines.append("S2 = \(responsePacketJunkSize!)") settingsLines.append("S2 = \(responsePacketJunkSize)")
settingsLines.append("H1 = \(initPacketMagicHeader!)") settingsLines.append("H1 = \(initPacketMagicHeader)")
settingsLines.append("H2 = \(responsePacketMagicHeader!)") settingsLines.append("H2 = \(responsePacketMagicHeader)")
settingsLines.append("H3 = \(underloadPacketMagicHeader!)") settingsLines.append("H3 = \(underloadPacketMagicHeader)")
settingsLines.append("H4 = \(transportPacketMagicHeader!)") settingsLines.append("H4 = \(transportPacketMagicHeader)")
// Optional parameters - only add if not nil and not empty // Optional parameters - only add if not nil and not empty
if let s3 = cookieReplyPacketJunkSize, !s3.isEmpty { if let s3 = trimmed(cookieReplyPacketJunkSize) {
settingsLines.append("S3 = \(s3)") settingsLines.append("S3 = \(s3)")
} }
if let s4 = transportPacketJunkSize, !s4.isEmpty { if let s4 = trimmed(transportPacketJunkSize) {
settingsLines.append("S4 = \(s4)") settingsLines.append("S4 = \(s4)")
} }
if let i1 = specialJunk1, !i1.isEmpty { if let i1 = trimmed(specialJunk1) {
settingsLines.append("I1 = \(i1)") settingsLines.append("I1 = \(i1)")
} }
if let i2 = specialJunk2, !i2.isEmpty { if let i2 = trimmed(specialJunk2) {
settingsLines.append("I2 = \(i2)") settingsLines.append("I2 = \(i2)")
} }
if let i3 = specialJunk3, !i3.isEmpty { if let i3 = trimmed(specialJunk3) {
settingsLines.append("I3 = \(i3)") settingsLines.append("I3 = \(i3)")
} }
if let i4 = specialJunk4, !i4.isEmpty { if let i4 = trimmed(specialJunk4) {
settingsLines.append("I4 = \(i4)") settingsLines.append("I4 = \(i4)")
} }
if let i5 = specialJunk5, !i5.isEmpty { if let i5 = trimmed(specialJunk5) {
settingsLines.append("I5 = \(i5)") settingsLines.append("I5 = \(i5)")
} }
if let j1 = controlledJunk1, !j1.isEmpty {
settingsLines.append("J1 = \(j1)")
}
if let j2 = controlledJunk2, !j2.isEmpty {
settingsLines.append("J2 = \(j2)")
}
if let j3 = controlledJunk3, !j3.isEmpty {
settingsLines.append("J3 = \(j3)")
}
if let itime = specialHandshakeTimeout, !itime.isEmpty {
settingsLines.append("Itime = \(itime)")
}
return settingsLines.joined(separator: "\n") return settingsLines.joined(separator: "\n")
} }
+32 -2
View File
@@ -2,6 +2,13 @@
#define IOS_CONTROLLER_H #define IOS_CONTROLLER_H
#include "protocols/vpnprotocol.h" #include "protocols/vpnprotocol.h"
#include <functional>
#include <QVariant>
#include <QVariantMap>
#include <QStringList>
#include <QList>
#include <QElapsedTimer>
#include <atomic>
#ifdef __OBJC__ #ifdef __OBJC__
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@@ -55,7 +62,24 @@ public:
bool shareText(const QStringList &filesToSend); bool shareText(const QStringList &filesToSend);
QString openFile(); QString openFile();
void purchaseProduct(const QString &productId,
std::function<void(bool success,
const QString &transactionId,
const QString &purchasedProductId,
const QString &originalTransactionId,
const QString &errorString)> &&callback);
void restorePurchases(std::function<void(bool success,
const QList<QVariantMap> &transactions,
const QString &errorString)> &&callback);
// Fetch product info for given product identifiers and return basic fields for logging
void fetchProducts(const QStringList &productIds,
std::function<void(const QList<QVariantMap> &products,
const QStringList &invalidIds,
const QString &errorString)> &&callback);
void requestInetAccess(); void requestInetAccess();
bool isTestFlight();
signals: signals:
void connectionStateChanged(Vpn::ConnectionState state); void connectionStateChanged(Vpn::ConnectionState state);
void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
@@ -81,6 +105,7 @@ private:
bool startXray(const QString &jsonConfig); bool startXray(const QString &jsonConfig);
void startTunnel(); void startTunnel();
void emitConnectionStateIfChanged(Vpn::ConnectionState state);
private: private:
void *m_iosControllerWrapper {}; void *m_iosControllerWrapper {};
@@ -94,8 +119,13 @@ private:
amnezia::Proto m_proto; amnezia::Proto m_proto;
QJsonObject m_rawConfig; QJsonObject m_rawConfig;
QString m_tunnelId; QString m_tunnelId;
uint64_t m_txBytes; uint64_t m_txBytes = 0;
uint64_t m_rxBytes; uint64_t m_rxBytes = 0;
bool m_handshakeAwaiting = false;
bool m_handshakeConfirmed = false;
QElapsedTimer m_handshakeTimer;
Vpn::ConnectionState m_lastEmittedState = Vpn::ConnectionState::Unknown;
std::atomic_bool m_statusRequestInFlight { false };
}; };
#endif // IOS_CONTROLLER_H #endif // IOS_CONTROLLER_H
+267 -22
View File
@@ -10,6 +10,7 @@
#include "../protocols/vpnprotocol.h" #include "../protocols/vpnprotocol.h"
#import "ios_controller_wrapper.h" #import "ios_controller_wrapper.h"
#import "StoreKitController.h"
const char* Action::start = "start"; const char* Action::start = "start";
const char* Action::restart = "restart"; const char* Action::restart = "restart";
@@ -92,6 +93,48 @@ Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
} }
} }
namespace {
constexpr int kHandshakeTimeoutMs = 12000;
constexpr uint64_t kHandshakeRxThreshold = 4096;
bool isWireGuardBasedProto(amnezia::Proto proto) {
return proto == amnezia::Proto::WireGuard || proto == amnezia::Proto::Awg;
}
uint64_t uint64FromResponse(NSDictionary *response, NSString *key, uint64_t fallback = 0) {
id value = response[key];
if (!value || value == [NSNull null]) {
return fallback;
}
if ([value isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)value unsignedLongLongValue];
}
if ([value isKindOfClass:[NSString class]]) {
const char *str = [(NSString *)value UTF8String];
if (str && *str) {
return strtoull(str, nullptr, 10);
}
}
return fallback;
}
long long int64FromResponse(NSDictionary *response, NSString *key, long long fallback = 0) {
id value = response[key];
if (!value || value == [NSNull null]) {
return fallback;
}
if ([value isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)value longLongValue];
}
if ([value isKindOfClass:[NSString class]]) {
const char *str = [(NSString *)value UTF8String];
if (str && *str) {
return strtoll(str, nullptr, 10);
}
}
return fallback;
}
}
namespace { namespace {
IosController* s_instance = nullptr; IosController* s_instance = nullptr;
} }
@@ -101,6 +144,9 @@ IosController::IosController() : QObject()
s_instance = this; s_instance = this;
m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this]; m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this];
// Initialize StoreKitController early to start observing the payment queue
[StoreKitController sharedInstance];
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
removeObserver: (__bridge NSObject *)m_iosControllerWrapper]; removeObserver: (__bridge NSObject *)m_iosControllerWrapper];
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
@@ -110,6 +156,15 @@ IosController::IosController() : QObject()
} }
void IosController::emitConnectionStateIfChanged(Vpn::ConnectionState state)
{
if (m_lastEmittedState == state) {
return;
}
m_lastEmittedState = state;
emit connectionStateChanged(state);
}
IosController* IosController::Instance() { IosController* IosController::Instance() {
if (!s_instance) { if (!s_instance) {
s_instance = new IosController(); s_instance = new IosController();
@@ -276,33 +331,65 @@ void IosController::disconnectVpn()
void IosController::checkStatus() void IosController::checkStatus()
{ {
if (!m_currentTunnel) {
return;
}
if (m_currentTunnel.connection.status != NEVPNStatusConnected) {
return;
}
if (m_statusRequestInFlight.exchange(true)) {
return;
}
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action]; NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus]; NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus];
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId]; NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @""; NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue}; NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sendVpnExtensionMessage(message, [&](NSDictionary* response){ sendVpnExtensionMessage(message, [&](NSDictionary* response){
uint64_t txBytes = [response[@"tx_bytes"] intValue]; if (!response) {
uint64_t rxBytes = [response[@"rx_bytes"] intValue]; QMetaObject::invokeMethod(this, [this]() {
m_statusRequestInFlight = false;
uint64_t last_handshake_time_sec = 0; }, Qt::QueuedConnection);
#if !MACOS_NE return;
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) { const uint64_t txBytes = uint64FromResponse(response, @"tx_bytes");
disconnectVpn(); const uint64_t rxBytes = uint64FromResponse(response, @"rx_bytes");
qDebug() << "Invalid handshake time, disconnecting VPN."; const long long last_handshake_time_sec = int64FromResponse(response, @"last_handshake_time_sec");
}
#endif
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes); QMetaObject::invokeMethod(this, [this, txBytes, rxBytes, last_handshake_time_sec]() {
m_rxBytes = rxBytes; if (isWireGuardBasedProto(m_proto) && m_handshakeAwaiting) {
m_txBytes = txBytes; const bool hasHandshakeData = (last_handshake_time_sec >= 0);
const bool hasFreshHandshake = hasHandshakeData &&
((last_handshake_time_sec > 0) ||
(rxBytes >= kHandshakeRxThreshold) ||
(txBytes >= kHandshakeRxThreshold));
if (hasFreshHandshake) {
m_handshakeConfirmed = true;
m_handshakeAwaiting = false;
m_handshakeTimer.invalidate();
qDebug() << "IosController::checkStatus : handshake confirmed";
emitConnectionStateIfChanged(Vpn::ConnectionState::Connected);
} else if (m_handshakeTimer.isValid() &&
m_handshakeTimer.elapsed() > kHandshakeTimeoutMs) {
m_handshakeTimer.restart();
qDebug() << "IosController::checkStatus : handshake timed out, keeping tunnel alive";
emitConnectionStateIfChanged(Vpn::ConnectionState::Reconnecting);
}
}
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
m_statusRequestInFlight = false;
}, Qt::QueuedConnection);
});
}); });
} }
@@ -409,7 +496,22 @@ void IosController::vpnStatusDidChange(void *pNotification)
} }
} }
emit connectionStateChanged(iosStatusToState(session.status)); Vpn::ConnectionState nextState = iosStatusToState(session.status);
if (session.status == NEVPNStatusConnected && isWireGuardBasedProto(m_proto)) {
if (!m_handshakeConfirmed) {
nextState = Vpn::ConnectionState::Connecting;
if (!m_handshakeAwaiting) {
m_handshakeAwaiting = true;
m_handshakeTimer.restart();
}
}
} else if (session.status != NEVPNStatusConnected) {
m_handshakeAwaiting = false;
m_handshakeConfirmed = false;
m_handshakeTimer.invalidate();
m_statusRequestInFlight = false;
}
emitConnectionStateIfChanged(nextState);
} }
} }
@@ -670,10 +772,6 @@ bool IosController::setupAwg()
wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]); wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]);
wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]); wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]);
wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]); wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]);
wgConfig.insert(config_key::controlledJunk1, config[config_key::controlledJunk1]);
wgConfig.insert(config_key::controlledJunk2, config[config_key::controlledJunk2]);
wgConfig.insert(config_key::controlledJunk3, config[config_key::controlledJunk3]);
wgConfig.insert(config_key::specialHandshakeTimeout, config[config_key::specialHandshakeTimeout]);
QJsonDocument wgConfigDoc(wgConfig); QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact)); QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
@@ -799,6 +897,9 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
{ {
if (!m_currentTunnel) { if (!m_currentTunnel) {
qDebug() << "Cannot set an extension callback without a tunnel manager"; qDebug() << "Cannot set an extension callback without a tunnel manager";
if (callback) {
callback(nil);
}
return; return;
} }
@@ -808,6 +909,9 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
if (!data || error) { if (!data || error) {
qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:" qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:"
<< [error.localizedDescription UTF8String]; << [error.localizedDescription UTF8String];
if (callback) {
callback(nil);
}
return; return;
} }
@@ -838,11 +942,18 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler]; [session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
} else { } else {
qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist"; qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist";
if (callback) {
callback(nil);
}
return;
} }
if (sendError) { if (sendError) {
qDebug() << "Failed to send message to VpnExtension. Error:" qDebug() << "Failed to send message to VpnExtension. Error:"
<< [sendError.localizedDescription UTF8String]; << [sendError.localizedDescription UTF8String];
if (callback) {
callback(nil);
}
} }
} }
@@ -913,6 +1024,135 @@ QString IosController::openFile() {
return filePath; return filePath;
} }
void IosController::purchaseProduct(const QString &productId,
std::function<void(bool success,
const QString &transactionId,
const QString &purchasedProductId,
const QString &originalTransactionId,
const QString &errorString)> &&callback)
{
qInfo().noquote() << "[IAP][IosController] purchaseProduct called" << productId;
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
__block auto cb = std::move(callback);
[controller purchaseProduct:productId.toNSString() completion:^(BOOL s,
NSString * _Nullable transactionId,
NSString * _Nullable prodId,
NSString * _Nullable originalTxId,
NSError * _Nullable error) {
const QString txId = QString::fromUtf8((transactionId ?: @"").UTF8String);
const QString pId = QString::fromUtf8((prodId ?: @"").UTF8String);
const QString origTxId = QString::fromUtf8((originalTxId ?: @"").UTF8String);
const QString err = QString::fromUtf8((error.localizedDescription ?: @"").UTF8String);
qInfo().noquote() << "[IAP][IosController] purchase completion" << "success=" << s
<< "transactionId=" << txId << "originalTransactionId=" << origTxId
<< "productId=" << pId << "error=" << err;
if (cb) {
cb(s, txId, pId, origTxId, err);
}
}];
} else {
if (callback) {
callback(false, QString(), QString(), QString(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::restorePurchases(std::function<void(bool success,
const QList<QVariantMap> &transactions,
const QString &errorString)> &&callback)
{
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
__block auto cb = std::move(callback);
[controller restorePurchasesWithCompletion:^(BOOL s,
NSArray<NSDictionary *> * _Nullable restoredTransactions,
NSError * _Nullable error) {
QString err;
if (error) {
err = QString::fromUtf8(error.localizedDescription.UTF8String);
}
QList<QVariantMap> transactions;
for (NSDictionary *dict in restoredTransactions ?: @[]) {
QVariantMap transaction;
NSString *transactionId = dict[@"transactionId"];
NSString *productId = dict[@"productId"];
NSString *originalTransactionId = dict[@"originalTransactionId"];
if (transactionId) {
transaction.insert(QStringLiteral("transactionId"), QString::fromUtf8(transactionId.UTF8String));
}
if (productId) {
transaction.insert(QStringLiteral("productId"), QString::fromUtf8(productId.UTF8String));
}
if (originalTransactionId) {
transaction.insert(QStringLiteral("originalTransactionId"),
QString::fromUtf8(originalTransactionId.UTF8String));
}
transactions.push_back(transaction);
}
if (cb) {
cb(s, transactions, err);
}
}];
} else {
if (callback) {
callback(false, QList<QVariantMap>(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::fetchProducts(const QStringList &productIds,
std::function<void(const QList<QVariantMap> &products,
const QStringList &invalidIds,
const QString &errorString)> &&callback)
{
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
NSMutableSet<NSString *> *ids = [NSMutableSet setWithCapacity:productIds.size()];
for (const auto &pid : productIds) {
[ids addObject:pid.toNSString()];
}
__block auto cb = std::move(callback);
[controller fetchProductsWithIdentifiers:ids
completion:^(NSArray<NSDictionary *> * _Nonnull products,
NSArray<NSString *> * _Nonnull invalidIdentifiers,
NSError * _Nullable error) {
QList<QVariantMap> outProducts;
for (NSDictionary *p in products) {
QVariantMap m;
m["productId"] = QString::fromUtf8([p[@"productId"] UTF8String]);
m["title"] = QString::fromUtf8([p[@"title"] UTF8String]);
m["description"] = QString::fromUtf8([p[@"description"] UTF8String]);
m["price"] = QString::fromUtf8([p[@"price"] UTF8String]);
m["currencyCode"] = QString::fromUtf8([p[@"currencyCode"] UTF8String]);
outProducts.push_back(m);
}
QStringList invalid;
for (NSString *inv in invalidIdentifiers) {
invalid.push_back(QString::fromUtf8(inv.UTF8String));
}
QString err;
if (error) {
err = QString::fromUtf8(error.localizedDescription.UTF8String);
}
if (cb) {
cb(outProducts, invalid, err);
}
}];
} else {
if (callback) {
callback(QList<QVariantMap>(), QStringList(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::requestInetAccess() { void IosController::requestInetAccess() {
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"]; NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
if (!url) { if (!url) {
@@ -931,3 +1171,8 @@ void IosController::requestInetAccess() {
}]; }];
[task resume]; [task resume];
} }
bool IosController::isTestFlight() {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
return receiptURL && [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
}
@@ -34,6 +34,9 @@ void IOSNetworkWatcher::initialize() {
}); });
nw_path_monitor_start(m_networkMonitor); nw_path_monitor_start(m_networkMonitor);
// Call start() to initialize sleep/wake monitoring (will call MacOSNetworkWatcher::start() if this is macOS)
this->start();
//TODO IMPL FOR AMNEZIA //TODO IMPL FOR AMNEZIA
} }
@@ -165,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
if (rtm->rtm_type == RTN_THROW) { if (rtm->rtm_type == RTN_THROW) {
struct in_addr ip4; struct in_addr ip4;
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4); inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().first.toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4)); nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0); nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST; rtm->rtm_type = RTN_UNICAST;
@@ -143,12 +143,6 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
for (const QString& key : config.m_specialJunk.keys()) { for (const QString& key : config.m_specialJunk.keys()) {
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
} }
for (const QString& key : config.m_controlledJunk.keys()) {
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
}
if (!config.m_specialHandshakeTimeout.isEmpty()) {
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
}
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
@@ -41,6 +41,9 @@ void LinuxNetworkWatcher::initialize() {
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this, connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
&LinuxNetworkWatcher::unsecuredNetwork); &LinuxNetworkWatcher::unsecuredNetwork);
connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
&NetworkWatcherImpl::wakeup);
// Let's wait a few seconds to allow the UI to be fully loaded and shown. // Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because // This is not strictly needed, but it's better for user experience because
// it makes the UI faster to appear, plus it gives a bit of delay between the // it makes the UI faster to appear, plus it gives a bit of delay between the
@@ -33,7 +33,21 @@
#define NM_802_11_AP_SEC_WEAK_CRYPTO \ #define NM_802_11_AP_SEC_WEAK_CRYPTO \
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104) (NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
enum NMState {
NM_STATE_UNKNOWN = 0,
NM_STATE_ASLEEP = 10,
NM_STATE_DISCONNECTED = 20,
NM_STATE_DISCONNECTING = 30,
NM_STATE_CONNECTING = 40,
NM_STATE_CONNECTED_LOCAL = 50,
NM_STATE_CONNECTED_SITE = 60,
NM_STATE_CONNECTED_GLOBAL = 70
};
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager"; constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
constexpr const char* DBUS_NETWORKMANAGER_PATH = "/org/freedesktop/NetworkManager";
namespace { namespace {
Logger logger("LinuxNetworkWatcherWorker"); Logger logger("LinuxNetworkWatcherWorker");
@@ -73,7 +87,7 @@ void LinuxNetworkWatcherWorker::initialize() {
// documentation: // documentation:
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html // https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager", QDBusInterface nm(DBUS_NETWORKMANAGER, DBUS_NETWORKMANAGER_PATH,
DBUS_NETWORKMANAGER, QDBusConnection::systemBus()); DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
if (!nm.isValid()) { if (!nm.isValid()) {
logger.error() logger.error()
@@ -108,6 +122,12 @@ void LinuxNetworkWatcherWorker::initialize() {
SLOT(propertyChanged(QString, QVariantMap, QStringList))); SLOT(propertyChanged(QString, QVariantMap, QStringList)));
} }
QDBusConnection::systemBus().connect(DBUS_NETWORKMANAGER,
DBUS_NETWORKMANAGER_PATH,
DBUS_NETWORKMANAGER,
"StateChanged",
this, SLOT(NMStateChanged(quint32)));
if (m_devicePaths.isEmpty()) { if (m_devicePaths.isEmpty()) {
logger.warning() << "No wifi devices found"; logger.warning() << "No wifi devices found";
return; return;
@@ -173,5 +193,16 @@ void LinuxNetworkWatcherWorker::checkDevices() {
emit unsecuredNetwork(ssid, bssid); emit unsecuredNetwork(ssid, bssid);
break; break;
} }
} }
} }
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
{
if (state == NM_STATE_ASLEEP) {
emit wakeup();
}
logger.debug() << "NMStateChanged " << state;
}
@@ -23,6 +23,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
signals: signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId); void unsecuredNetwork(const QString& networkName, const QString& networkId);
void wakeup();
public slots: public slots:
void initialize(); void initialize();
@@ -30,6 +31,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
private slots: private slots:
void propertyChanged(QString interface, QVariantMap properties, void propertyChanged(QString interface, QVariantMap properties,
QStringList list); QStringList list);
void NMStateChanged(quint32 state);
private: private:
// We collect the list of DBus wifi network device paths during the // We collect the list of DBus wifi network device paths during the
+185
View File
@@ -0,0 +1,185 @@
/* 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 "linuxpingsender.h"
#include <arpa/inet.h>
#include <errno.h>
#include <linux/filter.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>
#include <unistd.h>
#include <QSocketNotifier>
#include <QtEndian>
#include "leakdetector.h"
#include "logger.h"
#include "qhostaddress.h"
namespace {
Logger logger("LinuxPingSender");
}
int LinuxPingSender::createSocket() {
// Try creating an ICMP socket. This would be the ideal choice, but it can
// fail depending on the kernel config (see: sys.net.ipv4.ping_group_range)
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (m_socket >= 0) {
m_ident = 0;
return m_socket;
}
if ((errno != EPERM) && (errno != EACCES)) {
return -1;
}
// As a fallback, create a raw socket, which requires root permissions
// or CAP_NET_RAW to be granted to the VPN client.
m_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (m_socket < 0) {
return -1;
}
m_ident = getpid() & 0xffff;
// Attach a BPF filter to discard everything but replies to our echo.
struct sock_filter bpf_prog[] = {
BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* Skip IP header. */
BPF_STMT(BPF_LD | BPF_H | BPF_IND, 4), /* Load icmp echo ident */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, m_ident, 1, 0), /* Ours? */
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected identifier. Reject. */
BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), /* Load icmp type */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected type. Reject. */
BPF_STMT(BPF_RET | BPF_K, ~0U), /* Packet passes the filter. */
};
struct sock_fprog filter = {
.len = sizeof(bpf_prog) / sizeof(struct sock_filter),
.filter = bpf_prog,
};
setsockopt(m_socket, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
return m_socket;
}
LinuxPingSender::LinuxPingSender(const QHostAddress& source, QObject* parent)
: PingSender(parent) {
MZ_COUNT_CTOR(LinuxPingSender);
logger.debug() << "LinuxPingSender(" + logger.sensitive(source.toString()) +
") created";
m_socket = createSocket();
if (m_socket < 0) {
logger.error() << "Socket creation error: " << strerror(errno);
return;
}
quint32 ipv4addr = INADDR_ANY;
if (!source.isNull()) {
ipv4addr = source.toIPv4Address();
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4addr);
if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
close(m_socket);
m_socket = -1;
logger.error() << "bind error:" << strerror(errno);
return;
}
m_notifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, this);
if (m_ident) {
connect(m_notifier, &QSocketNotifier::activated, this,
&LinuxPingSender::rawSocketReady);
} else {
connect(m_notifier, &QSocketNotifier::activated, this,
&LinuxPingSender::icmpSocketReady);
}
}
LinuxPingSender::~LinuxPingSender() {
MZ_COUNT_DTOR(LinuxPingSender);
if (m_socket >= 0) {
close(m_socket);
}
}
void LinuxPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
quint32 ipv4dest = dest.toIPv4Address();
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4dest);
struct icmphdr packet;
memset(&packet, 0, sizeof(packet));
packet.type = ICMP_ECHO;
packet.un.echo.id = htons(m_ident);
packet.un.echo.sequence = htons(sequence);
packet.checksum = inetChecksum(&packet, sizeof(packet));
int rc = sendto(m_socket, &packet, sizeof(packet), 0, (struct sockaddr*)&addr,
sizeof(addr));
if (rc < 0) {
logger.error() << "failed to send:" << strerror(errno);
if (errno == ENETUNREACH) {
emit criticalPingError();
}
}
}
void LinuxPingSender::icmpSocketReady() {
socklen_t slen = 0;
unsigned char data[2048];
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
if (rc <= 0) {
logger.error() << "recvfrom failed:" << strerror(errno);
return;
}
struct icmphdr packet;
if (rc >= (int)sizeof(packet)) {
memcpy(&packet, data, sizeof(packet));
if (packet.type == ICMP_ECHOREPLY) {
emit recvPing(htons(packet.un.echo.sequence));
}
}
}
void LinuxPingSender::rawSocketReady() {
socklen_t slen = 0;
unsigned char data[2048];
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
if (rc <= 0) {
logger.error() << "recvfrom failed:" << strerror(errno);
return;
}
// Check the IP header
const struct iphdr* ip = (struct iphdr*)data;
int iphdrlen = ip->ihl * 4;
if (rc < iphdrlen || iphdrlen < (int)sizeof(struct iphdr)) {
logger.error() << "malformed IP packet:" << strerror(errno);
return;
}
// Check the ICMP packet
struct icmphdr packet;
if (inetChecksum(data + iphdrlen, rc - iphdrlen) != 0) {
logger.warning() << "invalid checksum";
return;
}
if (rc >= (iphdrlen + (int)sizeof(packet))) {
memcpy(&packet, data + iphdrlen, sizeof(packet));
quint16 id = htons(m_ident);
if ((packet.type == ICMP_ECHOREPLY) && (packet.un.echo.id == id)) {
emit recvPing(htons(packet.un.echo.sequence));
}
}
}
+39
View File
@@ -0,0 +1,39 @@
/* 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/. */
#ifndef LINUXPINGSENDER_H
#define LINUXPINGSENDER_H
#include <QObject>
#include "../../mozilla/pingsender.h"
class QSocketNotifier;
class LinuxPingSender final : public PingSender {
Q_OBJECT
Q_DISABLE_COPY_MOVE(LinuxPingSender)
public:
LinuxPingSender(const QHostAddress& source, QObject* parent = nullptr);
~LinuxPingSender();
bool isValid() override { return (m_socket >= 0); };
void sendPing(const QHostAddress& dest, quint16 sequence) override;
private:
int createSocket();
private slots:
void rawSocketReady();
void icmpSocketReady();
private:
QSocketNotifier* m_notifier = nullptr;
int m_socket = -1;
quint16 m_ident = 0;
};
#endif // LINUXPINGSENDER_H
@@ -141,12 +141,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
for (const QString& key : config.m_specialJunk.keys()) { for (const QString& key : config.m_specialJunk.keys()) {
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
} }
for (const QString& key : config.m_controlledJunk.keys()) {
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
}
if (!config.m_specialHandshakeTimeout.isEmpty()) {
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
}
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
@@ -10,8 +10,31 @@
#include "../ios/iosnetworkwatcher.h" #include "../ios/iosnetworkwatcher.h"
#include "networkwatcherimpl.h" #include "networkwatcherimpl.h"
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/IOMessage.h>
class QString; class QString;
// Inspired by https://ladydebug.com/blog/2020/05/21/programmatically-capture-energy-saver-event-on-mac/
class PowerNotificationsListener
{
public:
PowerNotificationsListener(class MacOSNetworkWatcher* watcher) : m_watcher(watcher) {}
void registerForNotifications();
void cleanup();
private:
static void sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument);
private:
class MacOSNetworkWatcher* m_watcher = nullptr;
IONotificationPortRef notifyPortRef = nullptr; // notification port allocated by IORegisterForSystemPower
io_object_t notifierObj = IO_OBJECT_NULL; // notifier object, used to deregister later
io_connect_t rootPowerDomain = IO_OBJECT_NULL; // a reference to the Root Power Domain IOService
};
class MacOSNetworkWatcher final : public IOSNetworkWatcher { class MacOSNetworkWatcher final : public IOSNetworkWatcher {
public: public:
MacOSNetworkWatcher(QObject* parent); MacOSNetworkWatcher(QObject* parent);
@@ -25,6 +48,7 @@ class MacOSNetworkWatcher final : public IOSNetworkWatcher {
private: private:
void* m_delegate = nullptr; void* m_delegate = nullptr;
PowerNotificationsListener m_powerlistener;
}; };
#endif // MACOSNETWORKWATCHER_H #endif // MACOSNETWORKWATCHER_H
+242 -34
View File
@@ -6,6 +6,11 @@
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
#include <QProcess>
#include <QMetaObject>
#include <pthread.h>
#include <iostream>
#import <CoreWLAN/CoreWLAN.h> #import <CoreWLAN/CoreWLAN.h>
#import <Network/Network.h> #import <Network/Network.h>
@@ -13,6 +18,37 @@ namespace {
Logger logger("MacOSNetworkWatcher"); Logger logger("MacOSNetworkWatcher");
} }
// Global variables for CFRunLoop thread
static pthread_t g_powerThread;
static CFRunLoopRef g_powerRunLoop = nullptr;
static bool g_shouldStopPowerThread = false;
static PowerNotificationsListener* g_powerListener = nullptr;
// Thread function for dedicated CFRunLoop
void* powerMonitoringThread(void* arg) {
logger.debug() << "Power monitoring thread started";
PowerNotificationsListener* listener = static_cast<PowerNotificationsListener*>(arg);
// Get the runloop for this thread
g_powerRunLoop = CFRunLoopGetCurrent();
// Register for power notifications in this thread
listener->registerForNotifications();
// Run the CFRunLoop - this will block until CFRunLoopStop is called
while (!g_shouldStopPowerThread) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
}
// Cleanup
listener->cleanup();
g_powerRunLoop = nullptr;
logger.debug() << "Power monitoring thread finished";
return nullptr;
}
@interface MacOSNetworkWatcherDelegate : NSObject <CWEventDelegate> { @interface MacOSNetworkWatcherDelegate : NSObject <CWEventDelegate> {
MacOSNetworkWatcher* m_watcher; MacOSNetworkWatcher* m_watcher;
} }
@@ -33,17 +69,145 @@ Logger logger("MacOSNetworkWatcher");
if (m_watcher) { if (m_watcher) {
m_watcher->checkInterface(); m_watcher->checkInterface();
// Emit networkChanged signal when BSSID changes
emit m_watcher->networkChanged(QString::fromNSString(interfaceName));
} }
} }
@end @end
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent) { void PowerNotificationsListener::registerForNotifications()
{
logger.debug() << "Registering for system power notifications in dedicated thread";
rootPowerDomain = IORegisterForSystemPower(this, &notifyPortRef, sleepWakeupCallBack, &notifierObj);
if (rootPowerDomain == IO_OBJECT_NULL) {
logger.error() << "Failed to register for system power notifications!";
return;
}
// Add the notification port to the current runloop (dedicated thread)
CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes);
logger.debug() << "Power notifications registered successfully";
}
void PowerNotificationsListener::cleanup()
{
if (notifyPortRef != nullptr) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes);
IONotificationPortDestroy(notifyPortRef);
notifyPortRef = nullptr;
}
if (notifierObj != IO_OBJECT_NULL) {
IODeregisterForSystemPower(&notifierObj);
notifierObj = IO_OBJECT_NULL;
}
if (rootPowerDomain != IO_OBJECT_NULL) {
IOServiceClose(rootPowerDomain);
rootPowerDomain = IO_OBJECT_NULL;
}
}
void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument)
{
Q_UNUSED(service)
auto listener = static_cast<PowerNotificationsListener *>(refParam);
logger.debug() << "Power callback received, messageType:" << messageType;
switch (messageType) {
case kIOMessageCanSystemSleep:
/* Idle sleep is about to kick in. This message will not be sent for forced sleep.
* Applications have a chance to prevent sleep by calling IOCancelPowerChange.
* Most applications should not prevent idle sleep. Power Management waits up to
* 30 seconds for you to either allow or deny idle sleep. If you dont acknowledge
* this power change by calling either IOAllowPowerChange or IOCancelPowerChange,
* the system will wait 30 seconds then go to sleep.
*/
logger.debug() << "System power message: can system sleep?";
// Uncomment to cancel idle sleep
// IOCancelPowerChange(thiz->rootPowerDomain, reinterpret_cast<long>(messageArgument));
// Allow idle sleep
IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast<long>(messageArgument));
break;
case kIOMessageSystemWillNotSleep:
/* Announces that the system has retracted a previous attempt to sleep; it
* follows `kIOMessageCanSystemSleep`.
*/
logger.debug() << "System power message: system will NOT sleep.";
break;
case kIOMessageSystemWillSleep:
/* The system WILL go to sleep. If you do not call IOAllowPowerChange or
* IOCancelPowerChange to acknowledge this message, sleep will be delayed by
* 30 seconds.
*
* NOTE: If you call IOCancelPowerChange to deny sleep it returns kIOReturnSuccess,
* however the system WILL still go to sleep.
*/
logger.debug() << "System power message: system WILL sleep";
IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast<long>(messageArgument));
break;
case kIOMessageSystemWillPowerOn:
/* Announces that the system is beginning to power the device tree; most devices
* are still unavailable at this point.
*/
/* From the documentation:
*
* - kIOMessageSystemWillPowerOn is delivered at early wakeup time, before most hardware
* has been powered on. Be aware that any attempts to access disk, network, the display,
* etc. may result in errors or blocking your process until those resources become
* available.
*
* So we do NOT log this event.
*/
break;
case kIOMessageSystemHasPoweredOn:
/* Announces that the system and its devices have woken up. */
logger.debug() << "System has powered on - emitting wakeup signal from dedicated CFRunLoop thread";
if (listener->m_watcher) {
// Use QMetaObject::invokeMethod for thread-safe signal emission
QMetaObject::invokeMethod(listener->m_watcher, "wakeup", Qt::QueuedConnection);
}
break;
default:
logger.debug() << "System power message: other event: " << messageType;
/* Not a system sleep and wake notification. */
break;
}
}
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent), m_powerlistener(this) {
MZ_COUNT_CTOR(MacOSNetworkWatcher); MZ_COUNT_CTOR(MacOSNetworkWatcher);
} }
MacOSNetworkWatcher::~MacOSNetworkWatcher() { MacOSNetworkWatcher::~MacOSNetworkWatcher() {
MZ_COUNT_DTOR(MacOSNetworkWatcher); MZ_COUNT_DTOR(MacOSNetworkWatcher);
// Stop the dedicated power monitoring thread
if (g_powerListener) {
logger.debug() << "Stopping dedicated power monitoring thread";
g_shouldStopPowerThread = true;
if (g_powerRunLoop) {
CFRunLoopStop(g_powerRunLoop);
}
// Wait for thread to finish
pthread_join(g_powerThread, nullptr);
g_powerListener = nullptr;
}
if (m_delegate) { if (m_delegate) {
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
if (!client) { if (!client) {
@@ -66,6 +230,20 @@ void MacOSNetworkWatcher::start() {
logger.debug() << "Delegate already registered"; logger.debug() << "Delegate already registered";
return; return;
} }
// Start dedicated power monitoring thread with CFRunLoop
if (!g_powerListener) {
g_powerListener = &m_powerlistener;
g_shouldStopPowerThread = false;
int result = pthread_create(&g_powerThread, nullptr, powerMonitoringThread, &m_powerlistener);
if (result != 0) {
logger.error() << "Failed to create power monitoring thread:" << result;
g_powerListener = nullptr;
} else {
logger.debug() << "Power monitoring enabled";
}
}
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
if (!client) { if (!client) {
@@ -77,6 +255,8 @@ void MacOSNetworkWatcher::start() {
m_delegate = [[MacOSNetworkWatcherDelegate alloc] initWithObject:this]; m_delegate = [[MacOSNetworkWatcherDelegate alloc] initWithObject:this];
[client setDelegate:static_cast<MacOSNetworkWatcherDelegate*>(m_delegate)]; [client setDelegate:static_cast<MacOSNetworkWatcherDelegate*>(m_delegate)];
[client startMonitoringEventWithType:CWEventTypeBSSIDDidChange error:nullptr]; [client startMonitoringEventWithType:CWEventTypeBSSIDDidChange error:nullptr];
logger.debug() << "MacOSNetworkWatcher started successfully";
} }
void MacOSNetworkWatcher::checkInterface() { void MacOSNetworkWatcher::checkInterface() {
@@ -87,42 +267,70 @@ void MacOSNetworkWatcher::checkInterface() {
return; return;
} }
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; // Use wdutil to get reliable WiFi information
if (!client) { QProcess process;
logger.debug() << "Unable to retrieve the CWWiFiClient shared instance"; process.start("wdutil", QStringList() << "info");
process.waitForFinished(5000);
QString output = process.readAllStandardOutput();
QString errorOutput = process.readAllStandardError();
logger.debug() << "wdutil exit code:" << process.exitCode();
if (process.exitCode() != 0) {
logger.debug() << "wdutil failed with exit code:" << process.exitCode();
return; return;
} }
CWInterface* interface = [client interface]; // Parse wdutil output to find WiFi connection info
if (!interface) { QStringList lines = output.split('\n');
logger.debug() << "No default wifi interface"; QString ssid, interfaceName, security;
return; bool wifiSectionFound = false;
for (int i = 0; i < lines.size(); i++) {
QString trimmedLine = lines[i].trimmed();
if (trimmedLine == "WIFI") {
wifiSectionFound = true;
continue;
}
if (wifiSectionFound) {
// Stop parsing when we reach next section header (all caps after separator line)
if (trimmedLine.startsWith("————————")) {
if (i + 1 < lines.size()) {
QString nextLine = lines[i + 1].trimmed();
if (!nextLine.isEmpty() && nextLine.length() > 2 && nextLine.toUpper() == nextLine && nextLine != "WIFI") {
break;
}
}
continue; // Skip separator lines
}
if (trimmedLine.startsWith("Interface Name")) {
QStringList parts = trimmedLine.split(":");
if (parts.size() >= 2) {
interfaceName = parts[1].trimmed();
}
} else if (trimmedLine.startsWith("SSID")) {
QStringList parts = trimmedLine.split(":");
if (parts.size() >= 2) {
ssid = parts[1].trimmed();
}
} else if (trimmedLine.startsWith("Security")) {
QStringList parts = trimmedLine.split(":");
if (parts.size() >= 2) {
security = parts[1].trimmed();
}
}
}
} }
if (![interface powerOn]) { if (!ssid.isEmpty() && !interfaceName.isEmpty()) {
logger.debug() << "The interface is off"; logger.debug() << "Found active WiFi connection on" << interfaceName
return; << "SSID:" << ssid << "Security:" << security;
} else {
logger.debug() << "No active WiFi connection found";
} }
NSString* ssidNS = [interface ssid];
if (!ssidNS) {
logger.debug() << "WiFi is not in used";
return;
}
QString ssid = QString::fromNSString(ssidNS);
if (ssid.isEmpty()) {
logger.debug() << "WiFi doesn't have a valid SSID";
return;
}
CWSecurity security = [interface security];
if (security == kCWSecurityNone || security == kCWSecurityWEP) {
logger.debug() << "Unsecured network found!";
emit unsecuredNetwork(ssid, ssid);
return;
}
logger.debug() << "Secure WiFi interface";
} }
@@ -22,7 +22,6 @@
#include "logger.h" #include "logger.h"
#include "platforms/windows/daemon/windowsfirewall.h" #include "platforms/windows/daemon/windowsfirewall.h"
#include "platforms/windows/daemon/windowssplittunnel.h" #include "platforms/windows/daemon/windowssplittunnel.h"
#include "platforms/windows/windowscommons.h"
#include "windowsfirewall.h" #include "windowsfirewall.h"
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
@@ -63,6 +62,9 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
} }
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) { void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (m_splitTunnelManager == nullptr)
return;
if (config.m_vpnDisabledApps.length() > 0) { if (config.m_vpnDisabledApps.length() > 0) {
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex); m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps); m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
@@ -32,9 +32,28 @@ WindowsNetworkWatcher::~WindowsNetworkWatcher() {
} }
} }
LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
auto obj = reinterpret_cast<WindowsNetworkWatcher*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (!obj){
logger.debug() << "obj not casted";
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_POWERBROADCAST:
if (wParam == PBT_APMRESUMESUSPEND) {
emit obj->wakeup();
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
void WindowsNetworkWatcher::initialize() { void WindowsNetworkWatcher::initialize() {
logger.debug() << "initialize"; logger.debug() << "initialize";
DWORD negotiatedVersion; DWORD negotiatedVersion;
if (WlanOpenHandle(2, nullptr, &negotiatedVersion, &m_wlanHandle) != if (WlanOpenHandle(2, nullptr, &negotiatedVersion, &m_wlanHandle) !=
ERROR_SUCCESS) { ERROR_SUCCESS) {
@@ -51,6 +70,25 @@ void WindowsNetworkWatcher::initialize() {
return; return;
} }
const wchar_t* className = L"PowerMonitorClass";
WNDCLASS wc = { 0 };
wc.lpfnWndProc = &WindowsNetworkWatcher::PowerWndProcCallback;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = className;
wc.cbWndExtra = sizeof(WindowsNetworkWatcher*);
if (!RegisterClass(&wc)) {
logger.debug() << "Failed to register window class in createPowerMonitorWindow.";
return;
}
HWND hwnd = CreateWindowEx(0, className, L"Power Monitor", 0, 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), static_cast<LPVOID>(this));
if (!hwnd) {
logger.debug() << "Failed to create window in createPowerMonitorWindow.";
return;
}
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
logger.debug() << "callback registered"; logger.debug() << "callback registered";
} }
@@ -137,4 +175,4 @@ void WindowsNetworkWatcher::processWlan(PWLAN_NOTIFICATION_DATA data) {
logger.debug() << "Unsecure network:" << logger.sensitive(ssid) logger.debug() << "Unsecure network:" << logger.sensitive(ssid)
<< "id:" << logger.sensitive(bssid); << "id:" << logger.sensitive(bssid);
emit unsecuredNetwork(ssid, bssid); emit unsecuredNetwork(ssid, bssid);
} }
@@ -19,6 +19,7 @@ class WindowsNetworkWatcher final : public NetworkWatcherImpl {
private: private:
static void wlanCallback(PWLAN_NOTIFICATION_DATA data, PVOID context); static void wlanCallback(PWLAN_NOTIFICATION_DATA data, PVOID context);
static LRESULT PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void processWlan(PWLAN_NOTIFICATION_DATA data); void processWlan(PWLAN_NOTIFICATION_DATA data);
@@ -179,6 +179,7 @@ void WindowsPingSender::pingEventReady() {
return; return;
} }
QString errmsg = WindowsUtils::getErrorMessage(); QString errmsg = WindowsUtils::getErrorMessage();
emit criticalPingError();
logger.error() << "No ping reply. Code: " << error logger.error() << "No ping reply. Code: " << error
<< " Message: " << errmsg; << " Message: " << errmsg;
return; return;
@@ -6,6 +6,7 @@
#include <chrono> #include <chrono>
#include "ipc.h"
#include "logger.h" #include "logger.h"
#include "ikev2_vpn_protocol_windows.h" #include "ikev2_vpn_protocol_windows.h"
#include "utilities.h" #include "utilities.h"
+60 -42
View File
@@ -7,7 +7,7 @@
#include <QNetworkInterface> #include <QNetworkInterface>
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
#include "logger.h" #include "ipc.h"
#include "openvpnprotocol.h" #include "openvpnprotocol.h"
#include "utilities.h" #include "utilities.h"
#include "version.h" #include "version.h"
@@ -56,7 +56,12 @@ void OpenVpnProtocol::stop()
} }
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->disableKillSwitch(); IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
}
});
#endif #endif
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
@@ -64,21 +69,24 @@ void OpenVpnProtocol::stop()
ErrorCode OpenVpnProtocol::prepare() ErrorCode OpenVpnProtocol::prepare()
{ {
if (!IpcClient::Interface()) { return IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<QStringList> listReply = iface->getTapList();
if (!listReply.waitForFinished(1000)) {
return ErrorCode::InternalError;
}
QStringList list = listReply.returnValue();
if (list.empty()) {
QRemoteObjectPendingReply<bool> installReply = iface->checkAndInstallDriver();
if (!installReply.waitForFinished() || !installReply.returnValue()) {
return ErrorCode::OpenVpnTapAdapterError;
}
}
return ErrorCode::NoError;
}, [] () {
return ErrorCode::AmneziaServiceConnectionFailed; return ErrorCode::AmneziaServiceConnectionFailed;
} });
QRemoteObjectPendingReply<QStringList> resultCheck = IpcClient::Interface()->getTapList();
resultCheck.waitForFinished();
if (resultCheck.returnValue().isEmpty()) {
QRemoteObjectPendingReply<bool> resultInstall = IpcClient::Interface()->checkAndInstallDriver();
resultInstall.waitForFinished();
if (!resultInstall.returnValue())
return ErrorCode::OpenVpnTapAdapterError;
}
return ErrorCode::NoError;
} }
void OpenVpnProtocol::killOpenVpnProcess() void OpenVpnProtocol::killOpenVpnProcess()
@@ -172,8 +180,17 @@ ErrorCode OpenVpnProtocol::start()
} }
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList(NetworkUtilities::getIPAddress( const ErrorCode res = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
m_configData.value(amnezia::config_key::hostName).toString()))); QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString());
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList(ip));
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
return ErrorCode::NoError;
});
if (res != ErrorCode::NoError) {
return res;
}
#endif #endif
// Detect default gateway // Detect default gateway
@@ -215,12 +232,6 @@ ErrorCode OpenVpnProtocol::start()
return ErrorCode::AmneziaServiceConnectionFailed; return ErrorCode::AmneziaServiceConnectionFailed;
} }
m_openVpnProcess->waitForSource(5000);
if (!m_openVpnProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->setProgram(PermittedProcess::OpenVPN); m_openVpnProcess->setProgram(PermittedProcess::OpenVPN);
QStringList arguments({ QStringList arguments({
"--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort), "--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort),
@@ -229,13 +240,13 @@ ErrorCode OpenVpnProtocol::start()
m_openVpnProcess->setArguments(arguments); m_openVpnProcess->setArguments(arguments);
qDebug() << arguments.join(" "); qDebug() << arguments.join(" ");
connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred, connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred,
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; }); [&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged, connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this,
[&]() { setConnectionState(Vpn::ConnectionState::Disconnected); }); [&]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_openVpnProcess->start(); m_openVpnProcess->start();
@@ -336,30 +347,37 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
m_vpnGateway = l.split(" ").at(2); m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QThread::msleep(300); QThread::msleep(300);
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces(); IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
for (int i = 0; i < netInterfaces.size(); i++) { QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) for (int i = 0; i < netInterfaces.size(); i++) {
{ for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
// killSwitch toggle {
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { // killSwitch toggle
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index()); if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
iface->enableKillSwitch(m_configData, netInterfaces.at(i).index());
}
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
iface->enablePeerTraffic(m_configData);
} }
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
IpcClient::Interface()->enablePeerTraffic(m_configData);
} }
} }
} });
#endif #endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
// killSwitch toggle // killSwitch toggle
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
m_configData.insert("vpnServer", m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString())); NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
IpcClient::Interface()->enableKillSwitch(m_configData, 0); IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(m_configData, 0);
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::updateVpnGateway(): Failed to enable killswitch";
}
});
} }
#endif #endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
+1 -1
View File
@@ -53,7 +53,7 @@ private:
void updateRouteGateway(QString line); void updateRouteGateway(QString line);
void updateVpnGateway(const QString &line); void updateVpnGateway(const QString &line);
QSharedPointer<PrivilegedProcess> m_openVpnProcess; QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
}; };
#endif // OPENVPNPROTOCOL_H #endif // OPENVPNPROTOCOL_H
+15
View File
@@ -1,6 +1,7 @@
#include "protocols_defs.h" #include "protocols_defs.h"
#include <QRandomGenerator> #include <QRandomGenerator>
#include <QJsonObject>
using namespace amnezia; using namespace amnezia;
@@ -217,3 +218,17 @@ QString ProtocolProps::key_proto_config_path(Proto p)
{ {
return protoToString(p) + "_config_path"; return protoToString(p) + "_config_path";
} }
QString ProtocolProps::getProtocolVersion(const QJsonObject &protocolConfig)
{
return protocolConfig.value(config_key::protocolVersion).toString();
}
QString ProtocolProps::getProtocolVersionString(const QJsonObject &protocolConfig)
{
auto version = getProtocolVersion(protocolConfig);
if (version == protocols::awg::awgV2) return QObject::tr(" (version 2)");
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
return "";
}
+11 -10
View File
@@ -83,10 +83,8 @@ namespace amnezia
constexpr char specialJunk3[] = "I3"; constexpr char specialJunk3[] = "I3";
constexpr char specialJunk4[] = "I4"; constexpr char specialJunk4[] = "I4";
constexpr char specialJunk5[] = "I5"; constexpr char specialJunk5[] = "I5";
constexpr char controlledJunk1[] = "J1";
constexpr char controlledJunk2[] = "J2"; constexpr char protocolVersion[] = "protocol_version";
constexpr char controlledJunk3[] = "J3";
constexpr char specialHandshakeTimeout[] = "Itime";
constexpr char openvpn[] = "openvpn"; constexpr char openvpn[] = "openvpn";
constexpr char wireguard[] = "wireguard"; constexpr char wireguard[] = "wireguard";
@@ -218,7 +216,8 @@ namespace amnezia
constexpr char defaultMtu[] = "1376"; constexpr char defaultMtu[] = "1376";
#endif #endif
constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf"; constexpr char serverConfigPath[] = "/opt/amnezia/awg/awg0.conf";
constexpr char serverLegacyConfigPath[] = "/opt/amnezia/awg/wg0.conf";
constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key"; constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key";
constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key"; constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key";
@@ -234,15 +233,14 @@ namespace amnezia
constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
constexpr char defaultSpecialJunk1[] = ""; constexpr char defaultSpecialJunk1[] = "<r 2><b 0x858000010001000000000669636c6f756403636f6d0000010001c00c000100010000105a00044d583737>";
constexpr char defaultSpecialJunk2[] = ""; constexpr char defaultSpecialJunk2[] = "";
constexpr char defaultSpecialJunk3[] = ""; constexpr char defaultSpecialJunk3[] = "";
constexpr char defaultSpecialJunk4[] = ""; constexpr char defaultSpecialJunk4[] = "";
constexpr char defaultSpecialJunk5[] = ""; constexpr char defaultSpecialJunk5[] = "";
constexpr char defaultControlledJunk1[] = "";
constexpr char defaultControlledJunk2[] = ""; constexpr char awgV1_5[] = "1.5";
constexpr char defaultControlledJunk3[] = ""; constexpr char awgV2[] = "2";
constexpr char defaultSpecialHandshakeTimeout[] = "";
} }
namespace socks5Proxy namespace socks5Proxy
@@ -325,6 +323,9 @@ namespace amnezia
Q_INVOKABLE static QString key_proto_config_data(Proto p); Q_INVOKABLE static QString key_proto_config_data(Proto p);
Q_INVOKABLE static QString key_proto_config_path(Proto p); Q_INVOKABLE static QString key_proto_config_path(Proto p);
static QString getProtocolVersion(const QJsonObject &protocolConfig);
static QString getProtocolVersionString(const QJsonObject &protocolConfig);
}; };
} // namespace amnezia } // namespace amnezia
+6
View File
@@ -103,6 +103,11 @@ QString VpnProtocol::vpnGateway() const
return m_vpnGateway; return m_vpnGateway;
} }
QString VpnProtocol::vpnLocalAddress() const
{
return m_vpnLocalAddress;
}
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
{ {
switch (container) { switch (container) {
@@ -114,6 +119,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
case DockerContainer::WireGuard: return new WireguardProtocol(configuration); case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
case DockerContainer::Awg2: return new WireguardProtocol(configuration);
case DockerContainer::Awg: return new WireguardProtocol(configuration); case DockerContainer::Awg: return new WireguardProtocol(configuration);
case DockerContainer::Xray: return new XrayProtocol(configuration); case DockerContainer::Xray: return new XrayProtocol(configuration);
case DockerContainer::SSXray: return new XrayProtocol(configuration); case DockerContainer::SSXray: return new XrayProtocol(configuration);
+2
View File
@@ -63,6 +63,7 @@ public:
QString routeGateway() const; QString routeGateway() const;
QString vpnGateway() const; QString vpnGateway() const;
QString vpnLocalAddress() const;
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration); static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
@@ -71,6 +72,7 @@ signals:
void connectionStateChanged(Vpn::ConnectionState state); void connectionStateChanged(Vpn::ConnectionState state);
void timeoutTimerEvent(); void timeoutTimerEvent();
void protocolError(amnezia::ErrorCode e); void protocolError(amnezia::ErrorCode e);
void tunnelAddressesUpdated(const QString& gateway, const QString& localAddress);
public slots: public slots:
virtual void onTimeout(); // todo: remove? virtual void onTimeout(); // todo: remove?
+22 -3
View File
@@ -15,10 +15,30 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
m_impl.reset(new LocalSocketController()); m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this, connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) { [this](const QString &pubkey, const QDateTime &connectionTimestamp) {
emit connectionStateChanged(Vpn::ConnectionState::Connected); setConnectionState(Vpn::ConnectionState::Connected);
}); });
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
[this](const QString& serverIpv4Gateway,
const QString& deviceIpv4Address, uint64_t txBytes,
uint64_t rxBytes) {
const QString previousGateway = m_vpnGateway;
const QString previousLocal = m_vpnLocalAddress;
if (!serverIpv4Gateway.isEmpty()) {
m_vpnGateway = serverIpv4Gateway;
}
if (!deviceIpv4Address.isEmpty()) {
m_vpnLocalAddress = deviceIpv4Address;
}
if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) ||
(!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) {
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
}
});
connect(m_impl.get(), &ControllerImpl::disconnected, this, connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); [this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_impl->initialize(nullptr, nullptr); m_impl->initialize(nullptr, nullptr);
} }
@@ -57,4 +77,3 @@ ErrorCode WireguardProtocol::start()
{ {
return startMzImpl(); return startMzImpl();
} }

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