From 35ecb8499d7078e0aa679ea3ca9c0465f4bf39ec Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 15 Jul 2023 14:19:48 -0700 Subject: [PATCH] WireGuard for MacOS (#248) * WireGuard for MacOS * Fix openvpn block-outside-dns --- .gitmodules | 3 + CMakeLists.txt | 22 +- client/3rd/wireguard-go | 1 + client/CMakeLists.txt | 291 ++-------- client/amnezia_application.cpp | 2 + client/android/build.gradle | 4 +- .../src/org/amnezia/vpn/NotificationUtil.kt | 2 +- .../android/src/org/amnezia/vpn/VPNLogger.kt | 4 +- .../amnezia/vpn/qt/PackageManagerHelper.java | 2 +- client/cmake/golang.cmake | 81 +-- client/cmake/ios.cmake | 159 ++++++ client/cmake/macos.cmake | 101 ++++ client/configurators/openvpn_configurator.cpp | 10 +- .../configurators/wireguard_configurator.cpp | 2 + client/configurators/wireguard_configurator.h | 1 + client/constants.h | 98 +--- client/containers/containers_defs.cpp | 8 +- client/core/servercontroller.cpp | 21 +- client/core/sshclient.cpp | 2 - client/daemon/daemon.cpp | 519 ++++++++++++++++++ client/daemon/daemon.h | 83 +++ client/daemon/daemonlocalserver.cpp | 98 ++++ client/daemon/daemonlocalserver.h | 26 + client/daemon/daemonlocalserverconnection.cpp | 162 ++++++ client/daemon/daemonlocalserverconnection.h | 36 ++ client/daemon/dnsutils.h | 34 ++ client/daemon/interfaceconfig.h | 31 ++ client/daemon/iputils.h | 31 ++ client/daemon/wireguardutils.h | 51 ++ client/logger.cpp | 79 +++ client/logger.h | 55 ++ client/mozilla/controllerimpl.h | 89 +++ client/mozilla/dnspingsender.cpp | 117 ++++ client/mozilla/dnspingsender.h | 29 + client/mozilla/localsocketcontroller.cpp | 384 +++++++++++++ client/mozilla/localsocketcontroller.h | 67 +++ client/mozilla/models/server.cpp | 209 +++++++ client/mozilla/models/server.h | 79 +++ client/mozilla/networkwatcher.cpp | 106 ++++ client/mozilla/networkwatcher.h | 49 ++ client/mozilla/networkwatcherimpl.h | 54 ++ client/mozilla/pinghelper.cpp | 189 +++++++ client/mozilla/pinghelper.h | 64 +++ client/mozilla/pingsender.cpp | 48 ++ client/mozilla/pingsender.h | 31 ++ client/mozilla/pingsenderfactory.cpp | 32 ++ client/mozilla/pingsenderfactory.h | 18 + client/mozilla/shared/ipaddress.cpp | 286 ++++++++++ .../ios => mozilla/shared}/ipaddress.h | 18 +- .../linux => mozilla/shared}/leakdetector.cpp | 10 +- .../linux => mozilla/shared}/leakdetector.h | 12 +- .../shared/loglevel.h} | 17 +- client/mozilla/shared/signalhandler.cpp | 76 +++ client/mozilla/shared/signalhandler.h | 31 ++ .../platforms/dummy/dummyapplistprovider.cpp | 29 + client/platforms/dummy/dummyapplistprovider.h | 20 + .../platforms/dummy/dummynetworkwatcher.cpp | 18 + client/platforms/dummy/dummynetworkwatcher.h | 22 + client/platforms/dummy/dummypingsender.cpp | 25 + client/platforms/dummy/dummypingsender.h | 21 + client/platforms/ios/bigint.h | 127 ----- client/platforms/ios/bigintipv6addr.h | 86 --- client/platforms/ios/iosadjusthelper.h | 16 - client/platforms/ios/iosadjusthelper.mm | 37 -- .../platforms/ios/iosauthenticationlistener.h | 23 - .../ios/iosauthenticationlistener.mm | 139 ----- client/platforms/ios/ioscontroller.h | 39 -- client/platforms/ios/ioscontroller.mm | 240 -------- client/platforms/ios/ioscontroller.swift | 288 ---------- client/platforms/ios/iosdatamigration.mm | 172 ------ client/platforms/ios/iosiaphandler.mm | 369 ------------- client/platforms/ios/iosinterface.swift | 230 -------- client/platforms/ios/iosnetworkwatcher.h | 32 ++ client/platforms/ios/iosnetworkwatcher.mm | 79 +++ .../platforms/ios/iosnotificationhandler.mm | 2 +- client/platforms/ios/iosopenvpn2ssadapter.h | 21 - client/platforms/ios/iosopenvpn2ssadapter.m | 139 ----- client/platforms/ios/iosutils.h | 17 - client/platforms/ios/iosutils.mm | 63 --- client/platforms/ios/ipaddress.cpp | 313 ----------- .../platforms/macos/daemon/dnsutilsmacos.cpp | 17 +- client/platforms/macos/daemon/dnsutilsmacos.h | 6 +- .../platforms/macos/daemon/iputilsmacos.cpp | 35 +- client/platforms/macos/daemon/iputilsmacos.h | 4 +- client/platforms/macos/daemon/macosdaemon.cpp | 34 +- client/platforms/macos/daemon/macosdaemon.h | 4 +- .../macos/daemon/macosdaemonserver.cpp | 60 -- .../macos/daemon/macosdaemonserver.h | 18 - .../macos/daemon/macosroutemonitor.cpp | 444 +++++++++++---- .../macos/daemon/macosroutemonitor.h | 27 +- .../macos/daemon/wireguardutilsmacos.cpp | 117 ++-- .../macos/daemon/wireguardutilsmacos.h | 17 +- client/platforms/macos/macoscryptosettings.mm | 136 ----- client/platforms/macos/macosmenubar.cpp | 106 ---- client/platforms/macos/macosmenubar.h | 42 -- client/platforms/macos/macosnetworkwatcher.h | 11 +- client/platforms/macos/macosnetworkwatcher.mm | 15 +- client/platforms/macos/macospingsender.cpp | 39 +- client/platforms/macos/macospingsender.h | 4 +- .../macos/macosstartatbootwatcher.cpp | 28 - .../platforms/macos/macosstartatbootwatcher.h | 22 - client/platforms/macos/macosstatusicon.h | 27 + client/platforms/macos/macosstatusicon.mm | 204 +++++++ client/platforms/macos/macosutils.h | 5 + client/platforms/macos/macosutils.mm | 125 ++++- client/protocols/protocols_defs.cpp | 10 - client/protocols/protocols_defs.h | 29 +- client/protocols/qml_register_protocols.h | 44 ++ client/protocols/wireguardprotocol.cpp | 62 ++- client/protocols/wireguardprotocol.h | 10 + client/ui/pages_logic/VpnLogic.cpp | 2 +- client/ui/systemtray_notificationhandler.cpp | 2 +- client/utilities.cpp | 4 +- service/server/CMakeLists.txt | 163 +++++- service/server/localserver.cpp | 17 + service/server/localserver.h | 10 + service/server/logger.cpp | 80 +++ service/server/logger.h | 55 ++ 118 files changed, 5150 insertions(+), 3486 deletions(-) create mode 160000 client/3rd/wireguard-go create mode 100644 client/cmake/ios.cmake create mode 100644 client/cmake/macos.cmake create mode 100644 client/daemon/daemon.cpp create mode 100644 client/daemon/daemon.h create mode 100644 client/daemon/daemonlocalserver.cpp create mode 100644 client/daemon/daemonlocalserver.h create mode 100644 client/daemon/daemonlocalserverconnection.cpp create mode 100644 client/daemon/daemonlocalserverconnection.h create mode 100644 client/daemon/dnsutils.h create mode 100644 client/daemon/interfaceconfig.h create mode 100644 client/daemon/iputils.h create mode 100644 client/daemon/wireguardutils.h create mode 100644 client/mozilla/controllerimpl.h create mode 100644 client/mozilla/dnspingsender.cpp create mode 100644 client/mozilla/dnspingsender.h create mode 100644 client/mozilla/localsocketcontroller.cpp create mode 100644 client/mozilla/localsocketcontroller.h create mode 100644 client/mozilla/models/server.cpp create mode 100644 client/mozilla/models/server.h create mode 100644 client/mozilla/networkwatcher.cpp create mode 100644 client/mozilla/networkwatcher.h create mode 100644 client/mozilla/networkwatcherimpl.h create mode 100644 client/mozilla/pinghelper.cpp create mode 100644 client/mozilla/pinghelper.h create mode 100644 client/mozilla/pingsender.cpp create mode 100644 client/mozilla/pingsender.h create mode 100644 client/mozilla/pingsenderfactory.cpp create mode 100644 client/mozilla/pingsenderfactory.h create mode 100644 client/mozilla/shared/ipaddress.cpp rename client/{platforms/ios => mozilla/shared}/ipaddress.h (80%) rename client/{platforms/linux => mozilla/shared}/leakdetector.cpp (91%) rename client/{platforms/linux => mozilla/shared}/leakdetector.h (82%) rename client/{platforms/ios/iosdatamigration.h => mozilla/shared/loglevel.h} (55%) create mode 100644 client/mozilla/shared/signalhandler.cpp create mode 100644 client/mozilla/shared/signalhandler.h create mode 100644 client/platforms/dummy/dummyapplistprovider.cpp create mode 100644 client/platforms/dummy/dummyapplistprovider.h create mode 100644 client/platforms/dummy/dummynetworkwatcher.cpp create mode 100644 client/platforms/dummy/dummynetworkwatcher.h create mode 100644 client/platforms/dummy/dummypingsender.cpp create mode 100644 client/platforms/dummy/dummypingsender.h delete mode 100644 client/platforms/ios/bigint.h delete mode 100644 client/platforms/ios/bigintipv6addr.h delete mode 100644 client/platforms/ios/iosadjusthelper.h delete mode 100644 client/platforms/ios/iosadjusthelper.mm delete mode 100644 client/platforms/ios/iosauthenticationlistener.h delete mode 100644 client/platforms/ios/iosauthenticationlistener.mm delete mode 100644 client/platforms/ios/ioscontroller.h delete mode 100644 client/platforms/ios/ioscontroller.mm delete mode 100644 client/platforms/ios/ioscontroller.swift delete mode 100644 client/platforms/ios/iosdatamigration.mm delete mode 100644 client/platforms/ios/iosiaphandler.mm delete mode 100644 client/platforms/ios/iosinterface.swift create mode 100644 client/platforms/ios/iosnetworkwatcher.h create mode 100644 client/platforms/ios/iosnetworkwatcher.mm delete mode 100644 client/platforms/ios/iosopenvpn2ssadapter.h delete mode 100644 client/platforms/ios/iosopenvpn2ssadapter.m delete mode 100644 client/platforms/ios/iosutils.h delete mode 100644 client/platforms/ios/iosutils.mm delete mode 100644 client/platforms/ios/ipaddress.cpp delete mode 100644 client/platforms/macos/daemon/macosdaemonserver.cpp delete mode 100644 client/platforms/macos/daemon/macosdaemonserver.h delete mode 100644 client/platforms/macos/macoscryptosettings.mm delete mode 100644 client/platforms/macos/macosmenubar.cpp delete mode 100644 client/platforms/macos/macosmenubar.h delete mode 100644 client/platforms/macos/macosstartatbootwatcher.cpp delete mode 100644 client/platforms/macos/macosstartatbootwatcher.h create mode 100644 client/platforms/macos/macosstatusicon.h create mode 100644 client/platforms/macos/macosstatusicon.mm create mode 100644 client/protocols/qml_register_protocols.h diff --git a/.gitmodules b/.gitmodules index 007eb375..c25da511 100644 --- a/.gitmodules +++ b/.gitmodules @@ -58,3 +58,6 @@ [submodule "client/3rd/mbedtls"] path = client/3rd/mbedtls url = https://github.com/Mbed-TLS/mbedtls.git +[submodule "client/3rd/wireguard-go"] + path = client/3rd/wireguard-go + url = https://github.com/WireGuard/wireguard-go/ diff --git a/CMakeLists.txt b/CMakeLists.txt index c8b27351..215de37f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,13 +2,31 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 3.0.7.5 +project(${PROJECT} VERSION 3.0.8.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-05-15") +set(RELEASE_DATE "2023-07-15") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(MZ_PLATFORM_NAME "linux") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(MZ_PLATFORM_NAME "windows") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(MZ_PLATFORM_NAME "macos") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android") + set(MZ_PLATFORM_NAME "android") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "iOS") + set(MZ_PLATFORM_NAME "ios") +elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") + set(MZ_PLATFORM_NAME "wasm") +endif() + +set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + if(ANDROID) set(QT_ANDROID_BUILD_ALL_ABIS ON) endif() diff --git a/client/3rd/wireguard-go b/client/3rd/wireguard-go new file mode 160000 index 00000000..052af4a8 --- /dev/null +++ b/client/3rd/wireguard-go @@ -0,0 +1 @@ +Subproject commit 052af4a8072bbbd3bfe7edf46fe3c1b350f71f08 diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index a7241eef..e90609e5 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -3,9 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) project(${PROJECT}) -set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) if(ANDROID) # For a some reason cmake do not applying GNU_SOURCE/BSD_SOURCE flags on Android platform. @@ -19,14 +16,14 @@ set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen") set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen") set(PACKAGES - Widgets Core Gui Network Xml - RemoteObjects Quick Svg QuickControls2 - Core5Compat Concurrent + Widgets Core Gui Network Xml + RemoteObjects Quick Svg QuickControls2 + Core5Compat Concurrent LinguistTools ) if(IOS) - set(PACKAGES - ${PACKAGES} - Multimedia + set(PACKAGES + ${PACKAGES} + Multimedia ) endif() @@ -39,13 +36,25 @@ set(LIBS ${LIBS} Qt6::Core5Compat Qt6::Concurrent ) if(IOS) - set(LIBS - ${LIBS} - Qt6::Multimedia - ) + set(LIBS + ${LIBS} + Qt6::Multimedia + ) endif() qt_standard_project_setup() +qt_add_executable(${PROJECT} MANUAL_FINALIZATION) + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) + qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep) +endif() + +qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) + +qt6_add_translations(${PROJECT} TS_FILES + ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts +) if(IOS) #execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/run-build-cloak.sh) @@ -62,6 +71,7 @@ if(IS_CI) endif() endif() + include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake) include_directories( @@ -82,8 +92,8 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h ${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.h - ${CMAKE_CURRENT_LIST_DIR}/platforms/linux/leakdetector.h ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h ${CMAKE_CURRENT_LIST_DIR}/ui/pages.h ${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h @@ -94,6 +104,19 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h ) +# Mozilla headres +set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h + ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h + ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h + ${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h + ${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h +) + +include_directories(mozilla) +include_directories(mozilla/shared) +include_directories(mozilla/models) + if(NOT IOS) set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h @@ -109,7 +132,6 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.cpp - ${CMAKE_CURRENT_LIST_DIR}/platforms/linux/leakdetector.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.cpp @@ -118,6 +140,18 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp ) +# Mozilla sources +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp + ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp + ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp + ${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp +) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG") +endif() + if(NOT IOS) set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp @@ -150,11 +184,7 @@ set(SOURCES ${SOURCES} ${UI_MODELS_CPP} ) -qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) - if(WIN32) - add_compile_definitions(MVPN_WINDOWS) - configure_file( ${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in ${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc @@ -205,40 +235,9 @@ if(APPLE) set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM}) set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER}) - if(NOT IOS) - set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE) - - message("MAC build") - set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ui/macos_util.h) - set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/ui/macos_util.mm) - - set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) - add_compile_definitions(MVPN_MACOS) - - set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns) - set(MACOSX_BUNDLE_ICON_FILE app.icns) - set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - set(SOURCES ${SOURCES} ${ICON_FILE}) - - find_library(FW_COCOA Cocoa) - find_library(FW_APPLICATIONSERVICES ApplicationServices) - find_library(FW_CORESERVICES CoreServices) - find_library(FW_FOUNDATION Foundation) - find_library(FW_APPKIT AppKit) - find_library(FW_SECURITY Security) - - set(LIBS ${LIBS} - ${FW_COCOA} ${FW_APPLICATIONSERVICES} - ${FW_FOUNDATION} ${FW_APPKIT} - ${FW_SECURITY} ${FW_CORESERVICES} - ${LIB_LIBCRYPTO} - ) - endif() endif() if(LINUX AND NOT ANDROID) - add_compile_definitions(MVPN_LINUX) - link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux) endif() @@ -273,8 +272,6 @@ if(ANDROID) # As QAndroidBinder is not yet implemented with a public api set(LIBS ${LIBS} Qt6::CorePrivate) - add_compile_definitions(MVPN_ANDROID) - link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/android) set(HEADERS ${HEADERS} @@ -295,179 +292,12 @@ if(ANDROID) endif() if(IOS) - message("Client iOS build") - - find_package(Qt6 REQUIRED COMPONENTS ShaderTools) - set(LIBS ${LIBS} Qt6::ShaderTools) - - find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices) - find_library(FW_UIKIT UIKit) - find_library(FW_AVFOUNDATION AVFoundation) - find_library(FW_FOUNDATION Foundation) - find_library(FW_STOREKIT StoreKit) - find_library(FW_USERNOTIFICATIONS UserNotifications) - - set(LIBS ${LIBS} - ${FW_AUTHENTICATIONSERVICES} ${FW_UIKIT} - ${FW_AVFOUNDATION} ${FW_FOUNDATION} ${FW_STOREKIT} - ${FW_USERNOTIFICATIONS} - ) - - add_compile_definitions(MVPN_IOS) - - set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/protocols/ios_vpnprotocol.h - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosnotificationhandler.h - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/bigint.h - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/bigintipv6addr.h - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddress.h - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddressrange.h - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate.h - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate-C-Interface.h - ) - - set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/protocols/ios_vpnprotocol.mm - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosnotificationhandler.mm - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosglue.mm - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddress.cpp - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ipaddressrange.cpp - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.mm - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QtAppDelegate.mm - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.mm - ) -endif() - -if(CMAKE_OSX_SYSROOT STREQUAL "iphoneos") - message("Building for iPhone OS") - set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0) -endif() - -qt_add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) -qt_add_translations(${PROJECT} TS_FILES - ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts) - -if(APPLE AND NOT IOS) - set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE) -endif() - -if(IOS) - enable_language(OBJC) - enable_language(OBJCXX) - enable_language(Swift) - + include(cmake/ios.cmake) + include(cmake/ios-arch-fixup.cmake) +elseif(APPLE AND NOT IOS) + include(cmake/golang.cmake) include(cmake/osxtools.cmake) - - set(LIBS ${LIBS} - ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/ios/iphone/libcrypto.a - ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenSSL/lib/ios/iphone/libssl.a - ) - - target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) - - set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) - - set_target_properties(${PROJECT} PROPERTIES - XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION - - MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/ios/app/Info.plist.in - MACOSX_BUNDLE_ICON_FILE "AppIcon" - MACOSX_BUNDLE_INFO_STRING "AmneziaVPN" - MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN" - MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}" - MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}" - MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}" - - XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}" - XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_LIST_DIR}/ios/app/main.entitlements" - XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}" - XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}" - XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN" - XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN" - - XCODE_GENERATE_SCHEME TRUE - XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" - XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" - XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" - - XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON - XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION - - XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" - - XCODE_EMBED_APP_EXTENSIONS networkextension - - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development" - - XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual - XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN" - XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN" - ) - - set_target_properties(${PROJECT} PROPERTIES - XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" - XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES" - XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_LIST_DIR}/platforms/ios/WireGuard-Bridging-Header.h" - XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO" - XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h" - ) - - set_target_properties(${PROJECT} PROPERTIES - XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK" - ) - - target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) - - target_compile_options(${PROJECT} PRIVATE - -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\" - -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" - ) - - target_sources(${PROJECT} PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/iosvpnprotocol.swift - ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/ioslogger.swift - - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Keychain.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c - ${CMAKE_CURRENT_LIST_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift - ) - - target_sources(${PROJECT} PRIVATE - ${CMAKE_CURRENT_LIST_DIR}/ios/app/launch.png - ${CMAKE_CURRENT_LIST_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard - ${CMAKE_CURRENT_LIST_DIR}/ios/Media.xcassets - ) - set_source_files_properties( - ${CMAKE_CURRENT_LIST_DIR}/ios/app/launch.png - ${CMAKE_CURRENT_LIST_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard - ${CMAKE_CURRENT_LIST_DIR}/ios/Media.xcassets - PROPERTIES MACOSX_PACKAGE_LOCATION "Resources" - ) - - - add_subdirectory(ios/networkextension) - add_dependencies(${PROJECT} networkextension) - - set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS - "${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework" - ) - - set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos) - target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_LIST_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework") - + include(cmake/macos.cmake) endif() if(ANDROID) @@ -522,11 +352,7 @@ if(ANDROID) endif() target_link_libraries(${PROJECT} PRIVATE ${LIBS}) - -if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) - qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) - qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep) -endif() +target_compile_definitions(${PROJECT} PRIVATE "MZ_$") # deploy artifacts required to run the application to the debug build folder if(WIN32) @@ -560,6 +386,7 @@ if(WIN32) COMMAND_EXPAND_LISTS ) endif() -if(IOS) - include(cmake/ios-arch-fixup.cmake) -endif() + + +target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) +qt_finalize_target(${PROJECT}) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 857d61fe..2f405550 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -38,6 +38,8 @@ #include "ui/pages_logic/protocols/OpenVpnLogic.h" #include "ui/pages_logic/protocols/ShadowSocksLogic.h" +#include "protocols/qml_register_protocols.h" + #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif diff --git a/client/android/build.gradle b/client/android/build.gradle index 26d8ee83..1a5b36ac 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -137,8 +137,8 @@ android { resConfig "en" minSdkVersion = 24 targetSdkVersion = 34 - versionCode 22 // Change to a higher number - versionName "3.0.7" // Change to a higher number + versionCode 25 // Change to a higher number + versionName "3.0.8" // Change to a higher number javaCompileOptions.annotationProcessorOptions.arguments = [ "room.schemaLocation": "${qtAndroidDir}/schemas".toString() diff --git a/client/android/src/org/amnezia/vpn/NotificationUtil.kt b/client/android/src/org/amnezia/vpn/NotificationUtil.kt index 4c9766ee..ee679f65 100644 --- a/client/android/src/org/amnezia/vpn/NotificationUtil.kt +++ b/client/android/src/org/amnezia/vpn/NotificationUtil.kt @@ -93,7 +93,7 @@ object NotificationUtil { val prefs = Prefs.get(service) val message = "" + prefs.getString("fallbackNotificationMessage", "Running in the Background") - val header = "" + prefs.getString("fallbackNotificationHeader", "Mozilla VPN") + val header = "" + prefs.getString("fallbackNotificationHeader", "Amnezia VPN") // Create the Intent that Should be Fired if the User Clicks the notification val mainActivityName = "org.amnezia.vpn.qt.VPNActivity" diff --git a/client/android/src/org/amnezia/vpn/VPNLogger.kt b/client/android/src/org/amnezia/vpn/VPNLogger.kt index e0709d24..e28dc471 100644 --- a/client/android/src/org/amnezia/vpn/VPNLogger.kt +++ b/client/android/src/org/amnezia/vpn/VPNLogger.kt @@ -11,14 +11,14 @@ import android.util.Log as nativeLog /* * Drop in replacement for android.util.Log - * Also stores a copy of all logs in tmp/mozilla_daemon_logs.txt + * Also stores a copy of all logs in tmp/amnezia_daemon_logs.txt */ class Log { val LOG_MAX_FILE_SIZE = 204800 private var file: File private constructor(context: Context) { val tempDIR = context.cacheDir - file = File(tempDIR, "mozilla_daemon_logs.txt") + file = File(tempDIR, "amnezia_daemon_logs.txt") if (file.length() > LOG_MAX_FILE_SIZE) { file.writeText("") } diff --git a/client/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java b/client/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java index ae0991f9..41c4cbf4 100644 --- a/client/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java +++ b/client/android/src/org/amnezia/vpn/qt/PackageManagerHelper.java @@ -111,7 +111,7 @@ public class PackageManagerHelper { // Returns List of all Packages that can classify themselves as browsers private static List getBrowserIDs(PackageManager pm) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.mozilla.org/")); + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.amnezia.org/")); intent.addCategory(Intent.CATEGORY_BROWSABLE); // We've tried using PackageManager.MATCH_DEFAULT_ONLY flag and found that browsers that // are not set as the default browser won't be matched even if they had CATEGORY_DEFAULT set diff --git a/client/cmake/golang.cmake b/client/cmake/golang.cmake index 21cc2725..d598af24 100644 --- a/client/cmake/golang.cmake +++ b/client/cmake/golang.cmake @@ -16,7 +16,7 @@ function(build_go_archive OUTPUT_NAME MODULE_FILE) string(REGEX REPLACE "\\.[^/]*$" ".h" GOBUILD_HEADER_FILE ${OUTPUT_NAME}) get_filename_component(GOBUILD_MODULE_ABS ${MODULE_FILE} ABSOLUTE) get_filename_component(GOBUILD_MODULE_DIR ${GOBUILD_MODULE_ABS} DIRECTORY) - set(GOBUILD_ARGS -buildmode=c-archive -trimpath -v) + set(GOBUILD_ARGS -buildmode=c-archive -buildvcs=false -trimpath -v) if(IS_DIRECTORY ${GOBUILD_MODULE_DIR}/vendor) list(APPEND GOBUILD_ARGS -mod vendor) endif() @@ -48,6 +48,8 @@ function(build_go_archive OUTPUT_NAME MODULE_FILE) DEPENDS ${MODULE_FILE} ${GOBUILD_SOURCES} WORKING_DIRECTORY ${GOBUILD_MODULE_DIR} COMMAND ${CMAKE_COMMAND} -E env GOCACHE=${GOCACHE} + CC=${CMAKE_C_COMPILER} + CXX=${CMAKE_CXX_COMPILER} CGO_ENABLED=1 CGO_CFLAGS="${GOBUILD_CGO_CFLAGS}" CGO_LDFLAGS="${GOBUILD_CGO_LDFLAGS}" @@ -61,64 +63,65 @@ endfunction(build_go_archive) function(add_go_library GOTARGET SOURCE) cmake_parse_arguments(GOLANG "" - "GOOS;GOARCH" + "" "CGO_CFLAGS;CGO_LDFLAGS" ${ARGN}) - get_filename_component(SRC_NAME ${SOURCE} NAME) get_filename_component(DIR_NAME ${SOURCE} DIRECTORY) get_filename_component(DIR_ABSOLUTE ${DIR_NAME} ABSOLUTE) - file(GLOB_RECURSE SRC_DEPS ${DIR_NAME}/*.go) set(HEADER_NAME "${GOTARGET}.h") set(ARCHIVE_NAME "${GOTARGET}${CMAKE_STATIC_LIBRARY_SUFFIX}") - set(GOCACHE ${CMAKE_BINARY_DIR}/go-cache) - set_directory_properties(PROPERTIES ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_BINARY_DIR}/go-cache) - set(GOFLAGS -buildmode=c-archive -trimpath -v) - if(IS_DIRECTORY ${DIR_NAME}/vendor) - set(GOFLAGS ${GOFLAGS} -mod vendor) - endif() - ## Add extras to the CGO compiler and linker flags. execute_process(OUTPUT_VARIABLE DEFAULT_CGO_CFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env CGO_CFLAGS) execute_process(OUTPUT_VARIABLE DEFAULT_CGO_LDFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env CGO_LDFLAGS) separate_arguments(DEFAULT_CGO_CFLAGS NATIVE_COMMAND ${DEFAULT_CGO_CFLAGS}) separate_arguments(DEFAULT_CGO_LDFLAGS NATIVE_COMMAND ${DEFAULT_CGO_LDFLAGS}) - list(PREPEND GOLANG_CGO_CFLAGS ${DEFAULT_CGO_CFLAGS}) - list(PREPEND GOLANG_CGO_LDFLAGS ${DEFAULT_CGO_LDFLAGS}) - if(NOT GOLANG_GOOS) - execute_process(OUTPUT_VARIABLE GOLANG_GOOS OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env GOOS) - endif() - if(NOT GOLANG_GOARCH) - execute_process(OUTPUT_VARIABLE GOLANG_GOARCH OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${GOLANG_BUILD_TOOL} env GOARCH) - endif() - - if(APPLE AND CMAKE_OSX_SYSROOT) - execute_process(OUTPUT_VARIABLE SDKROOT OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND xcrun --sdk ${CMAKE_OSX_SYSROOT} --show-sdk-path) - list(APPEND GOLANG_CGO_CFLAGS -isysroot ${SDKROOT}) - list(APPEND GOLANG_CGO_LDFLAGS -isysroot ${SDKROOT}) - endif() ## The actual commands that do the building. - add_custom_target(golang_${GOTARGET} - BYPRODUCTS ${ARCHIVE_NAME} ${HEADER_NAME} - WORKING_DIRECTORY ${DIR_ABSOLUTE} - SOURCES ${SRC_DEPS} ${DIR_NAME}/go.mod - COMMAND ${CMAKE_COMMAND} -E env GOCACHE=${GOCACHE} - CGO_ENABLED=1 - CGO_CFLAGS="${GOLANG_CGO_CFLAGS}" - CGO_LDFLAGS="${GOLANG_CGO_LDFLAGS}" - GOOS="${GOLANG_GOOS}" - GOARCH="${GOLANG_GOARCH}" - ${GOLANG_BUILD_TOOL} build ${GOFLAGS} -o ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME} ${SRC_NAME} + if((CMAKE_SYSTEM_NAME STREQUAL "Darwin") AND CMAKE_OSX_ARCHITECTURES) + foreach(OSXARCH ${CMAKE_OSX_ARCHITECTURES}) + string(REPLACE "x86_64" "amd64" GOARCH ${OSXARCH}) + build_go_archive(${CMAKE_CURRENT_BINARY_DIR}/${OSXARCH}/${ARCHIVE_NAME} ${DIR_NAME}/go.mod + GOARCH ${GOARCH} + CGO_CFLAGS ${DEFAULT_CGO_CFLAGS} ${GOLANG_CGO_CFLAGS} -arch ${OSXARCH} + CGO_LDFLAGS ${DEFAULT_CGO_LDFLAGS} ${GOLANG_CGO_LDFLAGS} -arch ${OSXARCH} + ) + list(APPEND ARCH_ARCHIVE_FILES ${CMAKE_CURRENT_BINARY_DIR}/${OSXARCH}/${ARCHIVE_NAME}) + list(APPEND ARCH_HEADER_FILES ${CMAKE_CURRENT_BINARY_DIR}/${OSXARCH}/${HEADER_NAME}) + endforeach() + + list(GET ARCH_HEADER_FILES 0 FIRST_HEADER_FILE) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME} + DEPENDS ${ARCH_ARCHIVE_FILES} ${ARCH_HEADER_FILES} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND lipo -create -output ${ARCHIVE_NAME} ${ARCH_ARCHIVE_FILES} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FIRST_HEADER_FILE} ${HEADER_NAME} + ) + else() + ## Regular single architecture build + build_go_archive(${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME} ${DIR_NAME}/go.mod + CGO_CFLAGS ${DEFAULT_CGO_CFLAGS} ${GOLANG_CGO_CFLAGS} + CGO_LDFLAGS ${DEFAULT_CGO_LDFLAGS} ${GOLANG_CGO_LDFLAGS} + ) + endif() + + set_source_files_properties({CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME} PROPERTIES + GENERATED TRUE + SKIP_AUTOGEN TRUE ) - set_target_properties(golang_${GOTARGET} PROPERTIES FOLDER "Libs") ## Wrap up the built library as an imported target. add_library(${GOTARGET} STATIC IMPORTED GLOBAL) - add_dependencies(${GOTARGET} golang_${GOTARGET}) set_target_properties(${GOTARGET} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR} INTERFACE_SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${HEADER_NAME} IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}) -endfunction(add_go_library) \ No newline at end of file + + ## Some dependency glue to ensure we actually build the library. + add_custom_target(${GOTARGET}_builder DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${ARCHIVE_NAME}) + add_dependencies(${GOTARGET} ${GOTARGET}_builder) + set_target_properties(${GOTARGET}_builder PROPERTIES FOLDER "Libs") + +endfunction(add_go_library) diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake new file mode 100644 index 00000000..b9265800 --- /dev/null +++ b/client/cmake/ios.cmake @@ -0,0 +1,159 @@ +message("Client iOS build") +set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0) +set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) + + +enable_language(OBJC) +enable_language(OBJCXX) +enable_language(Swift) + +find_package(Qt6 REQUIRED COMPONENTS ShaderTools) +set(LIBS ${LIBS} Qt6::ShaderTools) + +find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices) +find_library(FW_UIKIT UIKit) +find_library(FW_AVFOUNDATION AVFoundation) +find_library(FW_FOUNDATION Foundation) +find_library(FW_STOREKIT StoreKit) +find_library(FW_USERNOTIFICATIONS UserNotifications) + +set(LIBS ${LIBS} + ${FW_AUTHENTICATIONSERVICES} ${FW_UIKIT} + ${FW_AVFOUNDATION} ${FW_FOUNDATION} ${FW_STOREKIT} + ${FW_USERNOTIFICATIONS} +) + + +set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/ios_vpnprotocol.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h + #${CMAKE_CURRENT_SOURCE_DIR}/mozilla/shared/bigint.h + #${CMAKE_CURRENT_SOURCE_DIR}/mozilla/shared/bigintipv6addr.h + ${CMAKE_CURRENT_SOURCE_DIR}/mozilla/shared/ipaddress.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ipaddressrange.h # TODO need refactor + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h +) + +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/ios_vpnprotocol.mm + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm + ${CMAKE_CURRENT_SOURCE_DIR}/mozilla/shared/ipaddress.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ipaddressrange.cpp # TODO need refactor + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/MobileUtils.mm +) + +#set(SOURCES ${SOURCES} +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Keychain.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c +# ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift +#) + +set(LIBS ${LIBS} + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenSSL/lib/ios/iphone/libcrypto.a + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenSSL/lib/ios/iphone/libssl.a +) + +target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + + +set_target_properties(${PROJECT} PROPERTIES + XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in + MACOSX_BUNDLE_ICON_FILE "AppIcon" + MACOSX_BUNDLE_INFO_STRING "AmneziaVPN" + MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN" + MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}" + MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}" + MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}" + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/app/main.entitlements" + XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}" + XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}" + XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN" + XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN" + XCODE_GENERATE_SCHEME TRUE + XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" + XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" + XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" + XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON + XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION + XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" + XCODE_EMBED_APP_EXTENSIONS networkextension + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development" + XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual + XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN" + XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN" +) +set_target_properties(${PROJECT} PROPERTIES + XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" + XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES" + XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/WireGuard-Bridging-Header.h" + XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO" + XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h" +) +set_target_properties(${PROJECT} PROPERTIES + XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK" +) +target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_compile_options(${PROJECT} PRIVATE + -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\" + -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" +) +target_sources(${PROJECT} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioslogger.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Keychain.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift +) + +target_sources(${PROJECT} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/launch.png + ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard + ${CMAKE_CURRENT_SOURCE_DIR}/ios/Media.xcassets +) + +set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/launch.png + ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard + ${CMAKE_CURRENT_SOURCE_DIR}/ios/Media.xcassets +) + +add_subdirectory(ios/networkextension) +add_dependencies(${PROJECT} networkextension) + +set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS + "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework" +) + +set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos) +target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework") + diff --git a/client/cmake/macos.cmake b/client/cmake/macos.cmake new file mode 100644 index 00000000..ec173da4 --- /dev/null +++ b/client/cmake/macos.cmake @@ -0,0 +1,101 @@ +message("MAC build") + +set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE) +set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE) +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) + + +set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h +) + +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm +) + +set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns) +set(MACOSX_BUNDLE_ICON_FILE app.icns) +set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) +set(SOURCES ${SOURCES} ${ICON_FILE}) + + + +find_library(FW_SYSTEMCONFIG SystemConfiguration) +find_library(FW_SERVICEMGMT ServiceManagement) +find_library(FW_SECURITY Security) +find_library(FW_COREWLAN CoreWLAN) +find_library(FW_NETWORK Network) +find_library(FW_USER_NOTIFICATIONS UserNotifications) + +target_link_libraries(${PROJECT} PRIVATE ${FW_SYSTEMCONFIG}) +target_link_libraries(${PROJECT} PRIVATE ${FW_SERVICEMGMT}) +target_link_libraries(${PROJECT} PRIVATE ${FW_SECURITY}) +target_link_libraries(${PROJECT} PRIVATE ${FW_COREWLAN}) +target_link_libraries(${PROJECT} PRIVATE ${FW_NETWORK}) +target_link_libraries(${PROJECT} PRIVATE ${FW_USER_NOTIFICATIONS}) + +# Get SDK path +execute_process( + COMMAND sh -c "xcrun --sdk macosx --show-sdk-path" + OUTPUT_VARIABLE OSX_SDK_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE +) +message("OSX_SDK_PATH is: ${OSX_SDK_PATH}") + +# Build the Wireguard Go tunnel +file(GLOB_RECURSE WIREGUARD_GO_DEPS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-go/*.go) +set(WIREGUARD_GO_ENV + GOCACHE=${CMAKE_BINARY_DIR}/go-cache + CC=${CMAKE_C_COMPILER} + CXX=${CMAKE_CXX_COMPILER} + GOOS=darwin + CGO_ENABLED=1 + GO111MODULE=on + CGO_CFLAGS='-g -O3 -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} -isysroot ${OSX_SDK_PATH}' + CGO_LDFLAGS='-g -O3 -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET} -isysroot ${OSX_SDK_PATH}' +) + +if(NOT CMAKE_OSX_ARCHITECTURES) + foreach(OSXARCH ${CMAKE_OSX_ARCHITECTURES}) + message("Build wg for OSXARCH: ${OSXARCH}") + + string(REPLACE "x86_64" "amd64" GOARCH ${OSXARCH}) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-${OSXARCH} + COMMENT "Building wireguard-go for ${OSXARCH}" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-go + DEPENDS + ${WIREGUARD_GO_DEPS} + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-go/go.mod + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-go/go.sum + COMMAND ${CMAKE_COMMAND} -E env ${WIREGUARD_GO_ENV} GOARCH=${GOARCH} + ${GOLANG_BUILD_TOOL} build -buildmode exe -buildvcs=false -trimpath -v + -o ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-${OSXARCH} + ) + list(APPEND WG_GO_ARCH_BUILDS ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-${OSXARCH}/wireguard) + endforeach() + + add_custom_target(build_wireguard_go + COMMENT "Building wireguard-go" + DEPENDS ${WG_GO_ARCH_BUILDS} + COMMAND lipo -create -output ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go ${WG_GO_ARCH_BUILDS} + ) +else() + # This only builds for the host architecture. + add_custom_target(build_wireguard_go + COMMENT "Building wireguard-go" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-go + DEPENDS + ${WIREGUARD_GO_DEPS} + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-go/go.mod + ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-go/go.sum + COMMAND ${CMAKE_COMMAND} -E env ${WIREGUARD_GO_ENV} + ${GOLANG_BUILD_TOOL} build -buildmode exe -buildvcs=false -trimpath -v + -o ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go + ) +endif() +add_dependencies(${PROJECT} build_wireguard_go) +osx_bundle_files(${PROJECT} + FILES ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go + DESTINATION MacOS/ +) diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 608a9f68..4fad4612 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -95,7 +95,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia config.replace("", ""); } -#if defined Q_OS_MAC || defined(Q_OS_LINUX) +#ifndef MZ_WINDOWS config.replace("block-outside-dns", ""); #endif @@ -123,8 +123,11 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("redirect-gateway ipv6\n"); -#if (defined Q_OS_MAC || defined(Q_OS_LINUX)) && !defined(Q_OS_ANDROID) +#ifndef MZ_WINDOWS config.replace("block-outside-dns", ""); +#endif + +#if (defined (MZ_MACOS) || defined(MZ_LINUX)) QString dnsConf = QString( "\nscript-security 2\n" "up %1/update-resolv-conf.sh\n" @@ -147,9 +150,8 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig) config.append("redirect-gateway def1 bypass-dhcp\n"); } -#if (defined Q_OS_MAC || defined(Q_OS_LINUX)) && !defined(Q_OS_ANDROID) + // remove block-outside-dns for all exported configs config.replace("block-outside-dns", ""); -#endif json[config_key::config] = config; return QJsonDocument(json).toJson(); diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index df5bc0b3..54ee320c 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -63,6 +63,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; + connData.port = containerConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { if (errorCode) *errorCode = ErrorCode::InternalError; @@ -181,6 +182,7 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede jConfig[config_key::config] = config; jConfig[config_key::hostName] = connData.host; + jConfig[config_key::port] = connData.port.toInt(); jConfig[config_key::client_priv_key] = connData.clientPrivKey; jConfig[config_key::client_ip] = connData.clientIP; jConfig[config_key::client_pub_key] = connData.clientPubKey; diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index ab903d29..7674eb06 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -20,6 +20,7 @@ public: QString serverPubKey; // tls-auth key QString pskKey; // preshared key QString host; // host ip + QString port; }; QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, diff --git a/client/constants.h b/client/constants.h index 90f5e6eb..e72e7a2a 100644 --- a/client/constants.h +++ b/client/constants.h @@ -5,107 +5,31 @@ #ifndef CONSTANTS_H #define CONSTANTS_H +#include "version.h" + #include +#include namespace Constants { -// Returns true if we are in a production environment. -bool inProduction(); -void setStaging(); - -// Number of msecs for the captive-portal block alert. -constexpr uint32_t CAPTIVE_PORTAL_ALERT_MSEC = 4000; - -// Number of msecs for the unsecured network alert. -constexpr uint32_t UNSECURED_NETWORK_ALERT_MSEC = 4000; - -// Number of recent connections to retain. -constexpr int RECENT_CONNECTIONS_MAX_COUNT = 5; - -#if defined(UNIT_TEST) -# define CONSTEXPR(type, functionName, releaseValue, debugValue, \ - testingValue) \ - inline type functionName() { return testingValue; } -#else -# define CONSTEXPR(type, functionName, releaseValue, debugValue, \ - testingValue) \ - inline type functionName() { \ - return inProduction() ? releaseValue : debugValue; \ - } -#endif - -// Let's refresh the IP address any 10 minutes (in milliseconds). -CONSTEXPR(uint32_t, ipAddressTimerMsec, 600000, 10000, 0) - -// Let's check the connection status any second. -CONSTEXPR(uint32_t, checkStatusTimerMsec, 1000, 1000, 0) - -// Number of points for the charts. -CONSTEXPR(int, chartsMaxPoints, 30, 30, 30); - -// Any 6 hours, a new check -CONSTEXPR(uint32_t, releaseMonitorMsec, 21600000, 4000, 0) - -// in milliseconds, how often we should fetch the server list and the account. -CONSTEXPR(uint32_t, scheduleAccountAndServersTimerMsec, 3600000, 4000, 0) - -// how often we check the captive portal when the VPN is on. -CONSTEXPR(uint32_t, captivePortalRequestTimeoutMsec, 10000, 4000, 0) - -// How fast the animated icon should move -CONSTEXPR(uint32_t, statusIconAnimationMsec, 200, 200, 0) - -// How often glean pings are sent -CONSTEXPR(uint32_t, gleanTimeoutMsec, 1200000, 1000, 0) - -// How often we check the surveys to be executed (no network requests are done -// for this check) -CONSTEXPR(uint32_t, surveyTimerMsec, 300000, 4000, 0) - -#undef CONSTEXPR - -#define PRODBETAEXPR(type, functionName, prod, beta) \ - inline type functionName() { return inProduction() ? prod : beta; } - -constexpr const char* API_PRODUCTION_URL = "https://vpn.mozilla.org"; -constexpr const char* API_STAGING_URL = - "https://stage-vpn.guardian.nonprod.cloudops.mozgcp.net"; - -constexpr const char* LOGO_URL = ":/ui/resources/logo-dock.png"; - -PRODBETAEXPR(const char*, fxaUrl, "https://api.accounts.firefox.com", - "https://api-accounts.stage.mozaws.net") -PRODBETAEXPR( - const char*, balrogUrl, - "https://aus5.mozilla.org/json/1/FirefoxVPN/%1/%2/release/update.json", - "https://stage.balrog.nonprod.cloudops.mozgcp.net/json/1/FirefoxVPN/%1/%2/" - "release-cdntest/update.json"); -PRODBETAEXPR( - const char*, balrogRootCertFingerprint, - "97e8ba9cf12fb3de53cc42a4e6577ed64df493c247b414fea036818d3823560e", - "3c01446abe9036cea9a09acaa3a520ac628f20a7ae32ce861cb2efb70fa0c745"); - -#undef PRODBETAEXPR +inline QString versionString() { return QStringLiteral(APP_VERSION); } constexpr const char* PLATFORM_NAME = -#if defined(MVPN_IOS) +#if defined(MZ_IOS) "ios" -#elif defined(MVPN_MACOS) +#elif defined(MZ_MACOS) "macos" -#elif defined(MVPN_LINUX) +#elif defined(MZ_LINUX) "linux" -#elif defined(MVPN_ANDROID) +#elif defined(MZ_ANDROID) "android" -#elif defined(MVPN_WINDOWS) +#elif defined(MZ_WINDOWS) "windows" -#elif defined(UNIT_TEST) || defined(MVPN_DUMMY) +#elif defined(UNIT_TEST) || defined(MZ_DUMMY) "dummy" #else # error "Unsupported platform" #endif ; - -constexpr const char* PLACEHOLDER_USER_DNS = "127.0.0.1"; - -}; +} #endif // CONSTANTS_H diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 17391988..92066dab 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -161,7 +161,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) } #elif defined (Q_OS_MAC) switch (c) { - case DockerContainer::WireGuard: return false; + case DockerContainer::WireGuard: return true; case DockerContainer::Ipsec: return false; default: return true; } @@ -176,7 +176,11 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) } #elif defined (Q_OS_LINUX) - return true; + switch (c) { + case DockerContainer::WireGuard: return true; + case DockerContainer::Ipsec: return false; + default: return true; + } #else return false; diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 7f4690dc..b403c982 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -54,7 +54,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr script.replace("\r", ""); - qDebug() << "Run script"; + qDebug() << "ServerController::Run script"; QString totalLine; const QStringList &lines = script.split("\n", Qt::SkipEmptyParts); @@ -79,17 +79,16 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr continue; } - qDebug().noquote() << "EXEC" << lineToExec; + qDebug().noquote() << lineToExec; Logger::appendSshLog("Run command:" + lineToExec); - error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr); if (error != ErrorCode::NoError) { return error; } } - qDebug() << "ServerController::runScript finished\n"; + qDebug().noquote() << "ServerController::runScript finished\n"; return ErrorCode::NoError; } @@ -194,12 +193,6 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, }; *errorCode = runScript(credentials, script, cbReadStdOut); - - qDebug().noquote() << "Copy file from container stdout : \n" << stdOut; - - - qDebug().noquote() << "Copy file from container END : \n" ; - return QByteArray::fromHex(stdOut.toUtf8()); } @@ -216,8 +209,6 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential localFile.write(data); localFile.close(); - qDebug() << "remotePath" << remotePath; - error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), "non_desc"); if (error != ErrorCode::NoError) { return error; @@ -242,7 +233,6 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, QJsonObject &config, bool isUpdate) { qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container); - //qDebug().noquote() << QJsonDocument(config).toJson(); ErrorCode e = ErrorCode::NoError; e = isUserInSudo(credentials, container); @@ -430,8 +420,6 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), genVarsForScript(credentials, container, config)), cbReadStdOut); - qDebug() << "cbReadStdOut: " << stdOut; - if (stdOut.contains("docker: Error response from daemon")) return ErrorCode::ServerDockerFailedError; @@ -491,7 +479,6 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); - // Vars vars; @@ -616,10 +603,8 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars) { QString s = script; for (const QPair &var : vars) { - //qDebug() << "Replacing" << var.first << var.second; s.replace(var.first, var.second); } - //qDebug().noquote() << script; return s; } diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index cd58ba79..4c2a9591 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -143,8 +143,6 @@ namespace libssh { { output = std::string(buffer, bytesRead); if (!output.empty()) { - qDebug().noquote() << (isStdErr ? "stdErr" : "stdOut") << QString(output.c_str()); - if (cbReadStdOut && !isStdErr){ auto error = cbReadStdOut(output.c_str(), *this); if (error != ErrorCode::NoError) { diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp new file mode 100644 index 00000000..882f9508 --- /dev/null +++ b/client/daemon/daemon.cpp @@ -0,0 +1,519 @@ +/* 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 "daemon.h" + +#include +#include +#include +#include +#include +#include + +#include "leakdetector.h" +#include "logger.h" + +constexpr const char* JSON_ALLOWEDIPADDRESSRANGES = "allowedIPAddressRanges"; +constexpr int HANDSHAKE_POLL_MSEC = 250; + +namespace { + +Logger logger("Daemon"); + +Daemon* s_daemon = nullptr; + +} // namespace + +Daemon::Daemon(QObject* parent) : QObject(parent) { + MZ_COUNT_CTOR(Daemon); + + logger.debug() << "Daemon created"; + + Q_ASSERT(s_daemon == nullptr); + s_daemon = this; + + m_handshakeTimer.setSingleShot(true); + connect(&m_handshakeTimer, &QTimer::timeout, this, &Daemon::checkHandshake); +} + +Daemon::~Daemon() { + MZ_COUNT_DTOR(Daemon); + + logger.debug() << "Daemon released"; + + Q_ASSERT(s_daemon == this); + s_daemon = nullptr; +} + +// static +Daemon* Daemon::instance() { + Q_ASSERT(s_daemon); + return s_daemon; +} + +bool Daemon::activate(const InterfaceConfig& config) { + Q_ASSERT(wgutils() != nullptr); + + // There are 3 possible scenarios in which this method is called: + // + // 1. the VPN is off: the method tries to enable the VPN. + // 2. the VPN is on and the platform doesn't support the server-switching: + // this method calls deactivate() and then it continues as 1. + // 3. the VPN is on and the platform supports the server-switching: this + // method calls switchServer(). + // + // At the end, if the activation succeds, the `connected` signal is emitted. + logger.debug() << "Activating interface"; + + if (m_connections.contains(config.m_hopindex)) { + if (supportServerSwitching(config)) { + logger.debug() << "Already connected. Server switching supported."; + + if (!switchServer(config)) { + return false; + } + + if (supportDnsUtils() && !dnsutils()->restoreResolvers()) { + return false; + } + + if (!maybeUpdateResolvers(config)) { + return false; + } + + bool status = run(Switch, config); + logger.debug() << "Connection status:" << status; + if (status) { + m_connections[config.m_hopindex] = ConnectionState(config); + m_handshakeTimer.start(HANDSHAKE_POLL_MSEC); + } + return status; + } + + logger.warning() << "Already connected. Server switching not supported."; + if (!deactivate(false)) { + return false; + } + + Q_ASSERT(!m_connections.contains(config.m_hopindex)); + return activate(config); + } + + prepareActivation(config); + + // Bring up the wireguard interface if not already done. + if (!wgutils()->interfaceExists()) { + if (!wgutils()->addInterface(config)) { + logger.error() << "Interface creation failed."; + return false; + } + } + + // Configure routing for excluded addresses. + for (const QString& i : config.m_excludedAddresses) { + QHostAddress address(i); + if (m_excludedAddrSet.contains(address)) { + m_excludedAddrSet[address]++; + continue; + } + wgutils()->addExclusionRoute(address); + m_excludedAddrSet[address] = 1; + } + + // Add the peer to this interface. + if (!wgutils()->updatePeer(config)) { + logger.error() << "Peer creation failed."; + return false; + } + + if (!maybeUpdateResolvers(config)) { + return false; + } + + if (supportIPUtils()) { + if (!iputils()->addInterfaceIPs(config)) { + return false; + } + if (!iputils()->setMTUAndUp(config)) { + return false; + } + } + + // set routing + for (const IPAddress& ip : config.m_allowedIPAddressRanges) { + if (!wgutils()->updateRoutePrefix(ip, config.m_hopindex)) { + logger.debug() << "Routing configuration failed for" + << logger.sensitive(ip.toString()); + return false; + } + } + + bool status = run(Up, config); + logger.debug() << "Connection status:" << status; + if (status) { + m_connections[config.m_hopindex] = ConnectionState(config); + m_handshakeTimer.start(HANDSHAKE_POLL_MSEC); + } + + return status; +} + +bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) { + if ((config.m_hopindex == 0) && supportDnsUtils()) { + QList resolvers; + resolvers.append(QHostAddress(config.m_dnsServer)); + + // If the DNS is not the Gateway, it's a user defined DNS + // thus, not add any other :) + if (config.m_dnsServer == config.m_serverIpv4Gateway) { + resolvers.append(QHostAddress(config.m_serverIpv6Gateway)); + } + + if (!dnsutils()->updateResolvers(wgutils()->interfaceName(), resolvers)) { + return false; + } + } + + return true; +} + +// static +bool Daemon::parseStringList(const QJsonObject& obj, const QString& name, + QStringList& list) { + if (obj.contains(name)) { + QJsonValue value = obj.value(name); + if (!value.isArray()) { + logger.error() << name << "is not an array"; + return false; + } + QJsonArray array = value.toArray(); + for (const QJsonValue& i : array) { + if (!i.isString()) { + logger.error() << name << "must contain only strings"; + return false; + } + list.append(i.toString()); + } + } + return true; +} + +// static +bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { +#define GETVALUE(name, where, jsontype) \ + if (!obj.contains(name)) { \ + logger.debug() << name << " missing in the jsonConfig input"; \ + return false; \ + } else { \ + QJsonValue value = obj.value(name); \ + if (value.type() != QJsonValue::jsontype) { \ + logger.error() << name << " is not a " #jsontype; \ + return false; \ + } \ + where = value.to##jsontype(); \ + } + + GETVALUE("privateKey", config.m_privateKey, String); + GETVALUE("serverPublicKey", config.m_serverPublicKey, String); + GETVALUE("serverPort", config.m_serverPort, Double); + GETVALUE("serverPskKey", config.m_serverPskKey, String); + + config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString(); + config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString(); + if (config.m_deviceIpv4Address.isNull() && + config.m_deviceIpv6Address.isNull()) { + logger.warning() << "no device addresses found in jsonConfig input"; + return false; + } + config.m_serverIpv4AddrIn = obj.value("serverIpv4AddrIn").toString(); + config.m_serverIpv6AddrIn = obj.value("serverIpv6AddrIn").toString(); + if (config.m_serverIpv4AddrIn.isNull() && + config.m_serverIpv6AddrIn.isNull()) { + logger.error() << "no server addresses found in jsonConfig input"; + return false; + } + config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString(); + config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString(); + + if (!obj.contains("dnsServer")) { + config.m_dnsServer = QString(); + } else { + QJsonValue value = obj.value("dnsServer"); + if (!value.isString()) { + logger.error() << "dnsServer is not a string"; + return false; + } + config.m_dnsServer = value.toString(); + } + + if (!obj.contains("hopindex")) { + config.m_hopindex = 0; + } else { + QJsonValue value = obj.value("hopindex"); + if (!value.isDouble()) { + logger.error() << "hopindex is not a number"; + return false; + } + config.m_hopindex = value.toInt(); + } + + if (!obj.contains(JSON_ALLOWEDIPADDRESSRANGES)) { + logger.error() << JSON_ALLOWEDIPADDRESSRANGES + << "missing in the jsonconfig input"; + return false; + } else { + QJsonValue value = obj.value(JSON_ALLOWEDIPADDRESSRANGES); + if (!value.isArray()) { + logger.error() << JSON_ALLOWEDIPADDRESSRANGES << "is not an array"; + return false; + } + + QJsonArray array = value.toArray(); + for (const QJsonValue& i : array) { + if (!i.isObject()) { + logger.error() << JSON_ALLOWEDIPADDRESSRANGES + << "must contain only objects"; + return false; + } + + QJsonObject ipObj = i.toObject(); + + QJsonValue address = ipObj.value("address"); + if (!address.isString()) { + logger.error() << JSON_ALLOWEDIPADDRESSRANGES + << "objects must have a string address"; + return false; + } + + QJsonValue range = ipObj.value("range"); + if (!range.isDouble()) { + logger.error() << JSON_ALLOWEDIPADDRESSRANGES + << "object must have a numberic range"; + return false; + } + + QJsonValue isIpv6 = ipObj.value("isIpv6"); + if (!isIpv6.isBool()) { + logger.error() << JSON_ALLOWEDIPADDRESSRANGES + << "object must have a boolean isIpv6"; + return false; + } + + config.m_allowedIPAddressRanges.append( + IPAddress(QHostAddress(address.toString()), range.toInt())); + } + + // Sort allowed IPs by decreasing prefix length. + std::sort(config.m_allowedIPAddressRanges.begin(), + config.m_allowedIPAddressRanges.end(), + [&](const IPAddress& a, const IPAddress& b) -> bool { + return a.prefixLength() > b.prefixLength(); + }); + } + + if (!parseStringList(obj, "excludedAddresses", config.m_excludedAddresses)) { + return false; + } + if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) { + return false; + } + return true; +} + +bool Daemon::deactivate(bool emitSignals) { + Q_ASSERT(wgutils() != nullptr); + + // Deactivate the main interface. + if (m_connections.contains(0)) { + const ConnectionState& state = m_connections.value(0); + if (!run(Down, state.m_config)) { + return false; + } + } + + if (emitSignals) { + emit disconnected(); + } + + // Cleanup DNS + if (supportDnsUtils() && !dnsutils()->restoreResolvers()) { + return false; + } + + if (!wgutils()->interfaceExists()) { + logger.warning() << "Wireguard interface does not exist."; + return false; + } + + // Cleanup peers and routing + for (const ConnectionState& state : m_connections) { + const InterfaceConfig& config = state.m_config; + logger.debug() << "Deleting routes for hop" << config.m_hopindex; + for (const IPAddress& ip : config.m_allowedIPAddressRanges) { + wgutils()->deleteRoutePrefix(ip, config.m_hopindex); + } + wgutils()->deletePeer(config); + } + + // Cleanup routing for excluded addresses. + for (auto iterator = m_excludedAddrSet.constBegin(); + iterator != m_excludedAddrSet.constEnd(); ++iterator) { + wgutils()->deleteExclusionRoute(iterator.key()); + } + m_excludedAddrSet.clear(); + + // Delete the interface + if (!wgutils()->deleteInterface()) { + return false; + } + + m_connections.clear(); + return true; +} + +QString Daemon::logs() { + return {}; +} + +void Daemon::cleanLogs() { } + +bool Daemon::supportServerSwitching(const InterfaceConfig& config) const { + if (!m_connections.contains(config.m_hopindex)) { + return false; + } + const InterfaceConfig& current = + m_connections.value(config.m_hopindex).m_config; + + return current.m_privateKey == config.m_privateKey && + current.m_deviceIpv4Address == config.m_deviceIpv4Address && + current.m_deviceIpv6Address == config.m_deviceIpv6Address && + current.m_serverIpv4Gateway == config.m_serverIpv4Gateway && + current.m_serverIpv6Gateway == config.m_serverIpv6Gateway; +} + +bool Daemon::switchServer(const InterfaceConfig& config) { + Q_ASSERT(wgutils() != nullptr); + + logger.debug() << "Switching server for hop" << config.m_hopindex; + + Q_ASSERT(m_connections.contains(config.m_hopindex)); + const InterfaceConfig& lastConfig = + m_connections.value(config.m_hopindex).m_config; + + // Configure routing for new excluded addresses. + for (const QString& i : config.m_excludedAddresses) { + QHostAddress address(i); + if (m_excludedAddrSet.contains(address)) { + m_excludedAddrSet[address]++; + continue; + } + wgutils()->addExclusionRoute(address); + m_excludedAddrSet[address] = 1; + } + + // Activate the new peer and its routes. + if (!wgutils()->updatePeer(config)) { + logger.error() << "Server switch failed to update the wireguard interface"; + return false; + } + for (const IPAddress& ip : config.m_allowedIPAddressRanges) { + if (!wgutils()->updateRoutePrefix(ip, config.m_hopindex)) { + logger.error() << "Server switch failed to update the routing table"; + break; + } + } + + // Remove routing entries for the old peer. + for (const QString& i : lastConfig.m_excludedAddresses) { + QHostAddress address(i); + Q_ASSERT(m_excludedAddrSet.contains(address)); + if (m_excludedAddrSet[address] > 1) { + m_excludedAddrSet[address]--; + continue; + } + wgutils()->deleteExclusionRoute(address); + m_excludedAddrSet.remove(address); + } + for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) { + if (!config.m_allowedIPAddressRanges.contains(ip)) { + wgutils()->deleteRoutePrefix(ip, config.m_hopindex); + } + } + + // Remove the old peer if it is no longer necessary. + if (config.m_serverPublicKey != lastConfig.m_serverPublicKey) { + if (!wgutils()->deletePeer(lastConfig)) { + return false; + } + } + + m_connections[config.m_hopindex] = ConnectionState(config); + return true; +} + +QJsonObject Daemon::getStatus() { + Q_ASSERT(wgutils() != nullptr); + QJsonObject json; + logger.debug() << "Status request"; + + if (!m_connections.contains(0) || !wgutils()->interfaceExists()) { + json.insert("connected", QJsonValue(false)); + return json; + } + + const ConnectionState& connection = m_connections.value(0); + QList peers = wgutils()->getPeerStatus(); + for (const WireguardUtils::PeerStatus& status : peers) { + if (status.m_pubkey != connection.m_config.m_serverPublicKey) { + continue; + } + json.insert("connected", QJsonValue(true)); + json.insert("serverIpv4Gateway", + QJsonValue(connection.m_config.m_serverIpv4Gateway)); + json.insert("deviceIpv4Address", + QJsonValue(connection.m_config.m_deviceIpv4Address)); + json.insert("date", connection.m_date.toString()); + json.insert("txBytes", QJsonValue(status.m_txBytes)); + json.insert("rxBytes", QJsonValue(status.m_rxBytes)); + return json; + } + + json.insert("connected", QJsonValue(false)); + return json; +} + +void Daemon::checkHandshake() { + Q_ASSERT(wgutils() != nullptr); + + logger.debug() << "Checking for handshake..."; + + int pendingHandshakes = 0; + QList peers = wgutils()->getPeerStatus(); + for (ConnectionState& connection : m_connections) { + const InterfaceConfig& config = connection.m_config; + if (connection.m_date.isValid()) { + continue; + } + + // Check if the handshake has completed. + for (const WireguardUtils::PeerStatus& status : peers) { + if (config.m_serverPublicKey != status.m_pubkey) { + continue; + } + if (status.m_handshake != 0) { + connection.m_date.setMSecsSinceEpoch(status.m_handshake); + emit connected(status.m_pubkey); + } + } + + if (!connection.m_date.isValid()) { + pendingHandshakes++; + } + } + + // Check again if there were connections that haven't completed a handshake. + if (pendingHandshakes > 0) { + m_handshakeTimer.start(HANDSHAKE_POLL_MSEC); + } +} diff --git a/client/daemon/daemon.h b/client/daemon/daemon.h new file mode 100644 index 00000000..8046472f --- /dev/null +++ b/client/daemon/daemon.h @@ -0,0 +1,83 @@ +/* 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 DAEMON_H +#define DAEMON_H + +#include +#include + +#include "dnsutils.h" +#include "interfaceconfig.h" +#include "iputils.h" +#include "wireguardutils.h" + +class Daemon : public QObject { + Q_OBJECT + + public: + enum Op { + Up, + Down, + Switch, + }; + + explicit Daemon(QObject* parent); + ~Daemon(); + + static Daemon* instance(); + + static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config); + + virtual bool activate(const InterfaceConfig& config); + virtual bool deactivate(bool emitSignals = true); + virtual QJsonObject getStatus(); + + // Callback before any Activating measure is done + virtual void prepareActivation(const InterfaceConfig& config){ + Q_UNUSED(config)}; + + QString logs(); + void cleanLogs(); + + signals: + void connected(const QString& pubkey); + void disconnected(); + void backendFailure(); + + private: + bool maybeUpdateResolvers(const InterfaceConfig& config); + + protected: + virtual bool run(Op op, const InterfaceConfig& config) { + Q_UNUSED(op); + Q_UNUSED(config); + return true; + } + virtual bool supportServerSwitching(const InterfaceConfig& config) const; + virtual bool switchServer(const InterfaceConfig& config); + virtual WireguardUtils* wgutils() const = 0; + virtual bool supportIPUtils() const { return false; } + virtual IPUtils* iputils() { return nullptr; } + virtual bool supportDnsUtils() const { return false; } + virtual DnsUtils* dnsutils() { return nullptr; } + + static bool parseStringList(const QJsonObject& obj, const QString& name, + QStringList& list); + + void checkHandshake(); + + class ConnectionState { + public: + ConnectionState(){}; + ConnectionState(const InterfaceConfig& config) { m_config = config; } + QDateTime m_date; + InterfaceConfig m_config; + }; + QMap m_connections; + QHash m_excludedAddrSet; + QTimer m_handshakeTimer; +}; + +#endif // DAEMON_H diff --git a/client/daemon/daemonlocalserver.cpp b/client/daemon/daemonlocalserver.cpp new file mode 100644 index 00000000..02a12cb9 --- /dev/null +++ b/client/daemon/daemonlocalserver.cpp @@ -0,0 +1,98 @@ +/* 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 "daemonlocalserver.h" + +#include +#include +#include + +#include "daemonlocalserverconnection.h" +#include "leakdetector.h" +#include "logger.h" + +#ifdef MZ_MACOS +# include +# include +# include + +constexpr const char* TMP_PATH = "/tmp/amneziavpn.socket"; +constexpr const char* VAR_PATH = "/var/run/amneziavpn/daemon.socket"; +#endif + +namespace { +Logger logger("DaemonLocalServer"); +} // namespace + +DaemonLocalServer::DaemonLocalServer(QObject* parent) : QObject(parent) { + MZ_COUNT_CTOR(DaemonLocalServer); +} + +DaemonLocalServer::~DaemonLocalServer() { MZ_COUNT_DTOR(DaemonLocalServer); } + +bool DaemonLocalServer::initialize() { + m_server.setSocketOptions(QLocalServer::WorldAccessOption); + + QString path = daemonPath(); + logger.debug() << "Server path:" << path; + + if (QFileInfo::exists(path)) { + QFile::remove(path); + } + + if (!m_server.listen(path)) { + logger.error() << "Failed to listen the daemon path"; + return false; + } + + connect(&m_server, &QLocalServer::newConnection, [&] { + logger.debug() << "New connection received"; + + if (!m_server.hasPendingConnections()) { + return; + } + + QLocalSocket* socket = m_server.nextPendingConnection(); + Q_ASSERT(socket); + + DaemonLocalServerConnection* connection = + new DaemonLocalServerConnection(&m_server, socket); + connect(socket, &QLocalSocket::disconnected, connection, + &DaemonLocalServerConnection::deleteLater); + }); + + return true; +} + +QString DaemonLocalServer::daemonPath() const { +#if defined(MZ_WINDOWS) + return "\\\\.\\pipe\\amneziavpn"; +#elif defined(MZ_MACOS) + QDir dir("/var/run"); + if (!dir.exists()) { + logger.warning() << "/var/run doesn't exist. Fallback /tmp."; + return TMP_PATH; + } + + if (dir.exists("amneziavpn")) { + logger.debug() << "/var/run/amneziavpn seems to be usable"; + return VAR_PATH; + } + + if (!dir.mkdir("amneziavpn")) { + logger.warning() << "Failed to create /var/run/amneziavpn"; + return TMP_PATH; + } + + if (chmod("/var/run/amneziavpn", S_IRWXU | S_IRWXG | S_IRWXO) < 0) { + logger.warning() + << "Failed to set the right permissions to /var/run/amneziavpn"; + return TMP_PATH; + } + + return VAR_PATH; +#else +# error Unsupported platform +#endif +} diff --git a/client/daemon/daemonlocalserver.h b/client/daemon/daemonlocalserver.h new file mode 100644 index 00000000..f1960223 --- /dev/null +++ b/client/daemon/daemonlocalserver.h @@ -0,0 +1,26 @@ +/* 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 DAEMONLOCALSERVER_H +#define DAEMONLOCALSERVER_H + +#include + +class DaemonLocalServer final : public QObject { + Q_DISABLE_COPY_MOVE(DaemonLocalServer) + + public: + explicit DaemonLocalServer(QObject* parent); + ~DaemonLocalServer(); + + bool initialize(); + + private: + QString daemonPath() const; + + private: + QLocalServer m_server; +}; + +#endif // DAEMONLOCALSERVER_H diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp new file mode 100644 index 00000000..43c67f16 --- /dev/null +++ b/client/daemon/daemonlocalserverconnection.cpp @@ -0,0 +1,162 @@ +/* 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 "daemonlocalserverconnection.h" + +#include +#include +#include +#include + +#include "daemon.h" +#include "leakdetector.h" +#include "logger.h" + +namespace { +Logger logger("DaemonLocalServerConnection"); +} + +DaemonLocalServerConnection::DaemonLocalServerConnection(QObject* parent, + QLocalSocket* socket) + : QObject(parent) { + MZ_COUNT_CTOR(DaemonLocalServerConnection); + + logger.debug() << "Connection created"; + + Q_ASSERT(socket); + m_socket = socket; + + connect(m_socket, &QLocalSocket::readyRead, this, + &DaemonLocalServerConnection::readData); + + Daemon* daemon = Daemon::instance(); + connect(daemon, &Daemon::connected, this, + &DaemonLocalServerConnection::connected); + connect(daemon, &Daemon::disconnected, this, + &DaemonLocalServerConnection::disconnected); + connect(daemon, &Daemon::backendFailure, this, + &DaemonLocalServerConnection::backendFailure); +} + +DaemonLocalServerConnection::~DaemonLocalServerConnection() { + MZ_COUNT_DTOR(DaemonLocalServerConnection); + + logger.debug() << "Connection released"; +} + +void DaemonLocalServerConnection::readData() { + logger.debug() << "Read Data"; + + Q_ASSERT(m_socket); + + while (true) { + int pos = m_buffer.indexOf("\n"); + if (pos == -1) { + QByteArray input = m_socket->readAll(); + if (input.isEmpty()) { + break; + } + m_buffer.append(input); + continue; + } + + QByteArray line = m_buffer.left(pos); + m_buffer.remove(0, pos + 1); + + QByteArray command(line); + command = command.trimmed(); + + if (command.isEmpty()) { + continue; + } + + parseCommand(command); + } +} + +void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { + QJsonDocument json = QJsonDocument::fromJson(data); + if (!json.isObject()) { + logger.error() << "Invalid input"; + return; + } + + QJsonObject obj = json.object(); + QJsonValue typeValue = obj.value("type"); + if (!typeValue.isString()) { + logger.warning() << "No type command. Ignoring request."; + return; + } + QString type = typeValue.toString(); + + logger.debug() << "Command received:" << type; + + if (type == "activate") { + InterfaceConfig config; + if (!Daemon::parseConfig(obj, config)) { + logger.error() << "Invalid configuration"; + emit disconnected(); + return; + } + + if (!Daemon::instance()->activate(config)) { + logger.error() << "Failed to activate the interface"; + emit disconnected(); + } + return; + } + + if (type == "deactivate") { + Daemon::instance()->deactivate(); + return; + } + + if (type == "status") { + QJsonObject obj = Daemon::instance()->getStatus(); + obj.insert("type", "status"); + m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); + m_socket->write("\n"); + return; + } + + if (type == "logs") { + QJsonObject obj; + obj.insert("type", "logs"); + obj.insert("logs", Daemon::instance()->logs().replace("\n", "|")); + m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); + m_socket->write("\n"); + return; + } + + if (type == "cleanlogs") { + Daemon::instance()->cleanLogs(); + return; + } + + logger.warning() << "Invalid command:" << type; +} + +void DaemonLocalServerConnection::connected(const QString& pubkey) { + QJsonObject obj; + obj.insert("type", "connected"); + obj.insert("pubkey", QJsonValue(pubkey)); + write(obj); +} + +void DaemonLocalServerConnection::disconnected() { + QJsonObject obj; + obj.insert("type", "disconnected"); + write(obj); +} + +void DaemonLocalServerConnection::backendFailure() { + QJsonObject obj; + obj.insert("type", "backendFailure"); + write(obj); +} + +void DaemonLocalServerConnection::write(const QJsonObject& obj) { + m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact)); + m_socket->write("\n"); +} diff --git a/client/daemon/daemonlocalserverconnection.h b/client/daemon/daemonlocalserverconnection.h new file mode 100644 index 00000000..ec32df75 --- /dev/null +++ b/client/daemon/daemonlocalserverconnection.h @@ -0,0 +1,36 @@ +/* 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 DAEMONLOCALSERVERCONNECTION_H +#define DAEMONLOCALSERVERCONNECTION_H + +#include + +class QLocalSocket; + +class DaemonLocalServerConnection final : public QObject { + Q_DISABLE_COPY_MOVE(DaemonLocalServerConnection) + + public: + DaemonLocalServerConnection(QObject* parent, QLocalSocket* socket); + ~DaemonLocalServerConnection(); + + private: + void readData(); + + void parseCommand(const QByteArray& json); + + void connected(const QString& pubkey); + void disconnected(); + void backendFailure(); + + void write(const QJsonObject& obj); + + private: + QLocalSocket* m_socket = nullptr; + + QByteArray m_buffer; +}; + +#endif // DAEMONLOCALSERVERCONNECTION_H diff --git a/client/daemon/dnsutils.h b/client/daemon/dnsutils.h new file mode 100644 index 00000000..72e2d903 --- /dev/null +++ b/client/daemon/dnsutils.h @@ -0,0 +1,34 @@ +/* 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 DNSUTILS_H +#define DNSUTILS_H + +#include +#include + +#include "dnsutils.h" + +class DnsUtils : public QObject { + Q_OBJECT + + public: + explicit DnsUtils(QObject* parent) : QObject(parent){}; + virtual ~DnsUtils() = default; + + virtual bool updateResolvers(const QString& ifname, + const QList& resolvers) { + Q_UNUSED(ifname); + Q_UNUSED(resolvers); + qFatal("Have you forgotten to implement DnsUtils::updateResolvers?"); + return false; + }; + + virtual bool restoreResolvers() { + qFatal("Have you forgotten to implement DnsUtils::restoreResolvers?"); + return false; + } +}; + +#endif // DNSUTILS_H diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h new file mode 100644 index 00000000..51e8dd71 --- /dev/null +++ b/client/daemon/interfaceconfig.h @@ -0,0 +1,31 @@ +/* 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 INTERFACECONFIG_H +#define INTERFACECONFIG_H + +#include +#include + +#include "ipaddress.h" + +struct InterfaceConfig { + int m_hopindex = 0; + QString m_privateKey; + QString m_deviceIpv4Address; + QString m_deviceIpv6Address; + QString m_serverIpv4Gateway; + QString m_serverIpv6Gateway; + QString m_serverPublicKey; + QString m_serverPskKey; + QString m_serverIpv4AddrIn; + QString m_serverIpv6AddrIn; + QString m_dnsServer; + int m_serverPort = 0; + QList m_allowedIPAddressRanges; + QStringList m_excludedAddresses; + QStringList m_vpnDisabledApps; +}; + +#endif // INTERFACECONFIG_H diff --git a/client/daemon/iputils.h b/client/daemon/iputils.h new file mode 100644 index 00000000..a231be6c --- /dev/null +++ b/client/daemon/iputils.h @@ -0,0 +1,31 @@ +/* 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 IPUTILS_H +#define IPUTILS_H + +#include +#include + +#include "interfaceconfig.h" + +class IPUtils : public QObject { + public: + explicit IPUtils(QObject* parent) : QObject(parent){}; + virtual ~IPUtils() = default; + + virtual bool addInterfaceIPs(const InterfaceConfig& config) { + Q_UNUSED(config); + qFatal("Have you forgotten to implement IPUtils::addInterfaceIPs?"); + return false; + }; + + virtual bool setMTUAndUp(const InterfaceConfig& config) { + Q_UNUSED(config); + qFatal("Have you forgotten to implement IPUtils::setMTUAndUp?"); + return false; + }; +}; + +#endif // IPUTILS_H \ No newline at end of file diff --git a/client/daemon/wireguardutils.h b/client/daemon/wireguardutils.h new file mode 100644 index 00000000..278e2dfe --- /dev/null +++ b/client/daemon/wireguardutils.h @@ -0,0 +1,51 @@ +/* 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 WIREGUARDUTILS_H +#define WIREGUARDUTILS_H + +#include +#include +#include +#include + +#include "interfaceconfig.h" + +constexpr const char* WG_INTERFACE = "moz0"; + +constexpr uint16_t WG_KEEPALIVE_PERIOD = 60; + +class WireguardUtils : public QObject { + Q_OBJECT + + public: + class PeerStatus { + public: + PeerStatus(const QString& pubkey = QString()) { m_pubkey = pubkey; } + QString m_pubkey; + qint64 m_handshake = 0; + qint64 m_rxBytes = 0; + qint64 m_txBytes = 0; + }; + + explicit WireguardUtils(QObject* parent) : QObject(parent){}; + virtual ~WireguardUtils() = default; + + virtual bool interfaceExists() = 0; + virtual QString interfaceName() { return WG_INTERFACE; } + virtual bool addInterface(const InterfaceConfig& config) = 0; + virtual bool deleteInterface() = 0; + + virtual bool updatePeer(const InterfaceConfig& config) = 0; + virtual bool deletePeer(const InterfaceConfig& config) = 0; + virtual QList getPeerStatus() = 0; + + virtual bool updateRoutePrefix(const IPAddress& prefix, int hopindex) = 0; + virtual bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) = 0; + + virtual bool addExclusionRoute(const QHostAddress& address) = 0; + virtual bool deleteExclusionRoute(const QHostAddress& address) = 0; +}; + +#endif // WIREGUARDUTILS_H diff --git a/client/logger.cpp b/client/logger.cpp index 9a0dd077..e09792b1 100644 --- a/client/logger.cpp +++ b/client/logger.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -180,3 +182,80 @@ void Logger::cleanUp() clearServiceLogs(); } + +Logger::Log::Log(Logger* logger, LogLevel logLevel) + : m_logger(logger), m_logLevel(logLevel), m_data(new Data()) {} + +Logger::Log::~Log() { + qDebug() << "Amnezia" << m_logger->className() << m_data->m_buffer.trimmed(); + delete m_data; +} + +Logger::Log Logger::error() { return Log(this, LogLevel::Error); } +Logger::Log Logger::warning() { return Log(this, LogLevel::Warning); } +Logger::Log Logger::info() { return Log(this, LogLevel::Info); } +Logger::Log Logger::debug() { return Log(this, LogLevel::Debug); } +QString Logger::sensitive(const QString& input) { +#ifdef Q_DEBUG + return input; +#else + Q_UNUSED(input); + return QString(8, 'X'); +#endif +} + + +#define CREATE_LOG_OP_REF(x) \ +Logger::Log& Logger::Log::operator<<(x t) { \ + m_data->m_ts << t << ' '; \ + return *this; \ +} + +CREATE_LOG_OP_REF(uint64_t); +CREATE_LOG_OP_REF(const char*); +CREATE_LOG_OP_REF(const QString&); +CREATE_LOG_OP_REF(const QByteArray&); +CREATE_LOG_OP_REF(const void*); + +#undef CREATE_LOG_OP_REF + +Logger::Log& Logger::Log::operator<<(const QStringList& t) { + m_data->m_ts << '[' << t.join(",") << ']' << ' '; + return *this; +} + +Logger::Log& Logger::Log::operator<<(const QJsonObject& t) { + m_data->m_ts << QJsonDocument(t).toJson(QJsonDocument::Indented) << ' '; + return *this; +} + +Logger::Log& Logger::Log::operator<<(QTextStreamFunction t) { + m_data->m_ts << t; + return *this; +} + +void Logger::Log::addMetaEnum(quint64 value, const QMetaObject* meta, + const char* name) { + QMetaEnum me = meta->enumerator(meta->indexOfEnumerator(name)); + + QString out; + QTextStream ts(&out); + + if (const char* scope = me.scope()) { + ts << scope << "::"; + } + + const char* key = me.valueToKey(static_cast(value)); + const bool scoped = me.isScoped(); + if (scoped || !key) { + ts << me.enumName() << (!key ? "(" : "::"); + } + + if (key) { + ts << key; + } else { + ts << value << ")"; + } + + m_data->m_ts << out; +} diff --git a/client/logger.h b/client/logger.h index bea5213d..f8bfc225 100644 --- a/client/logger.h +++ b/client/logger.h @@ -9,6 +9,8 @@ #include "ui/property_helper.h" +#include "mozilla/shared/loglevel.h" + class Logger : public QObject { Q_OBJECT @@ -34,6 +36,56 @@ public: static QString userLogsFilePath(); static QString getLogFile(); + // compat with Mozilla logger + Logger(const QString &className) { m_className = className; } + const QString& className() const { return m_className; } + + class Log { + public: + Log(Logger* logger, LogLevel level); + ~Log(); + + Log& operator<<(uint64_t t); + Log& operator<<(const char* t); + Log& operator<<(const QString& t); + Log& operator<<(const QStringList& t); + Log& operator<<(const QByteArray& t); + Log& operator<<(const QJsonObject& t); + Log& operator<<(QTextStreamFunction t); + Log& operator<<(const void* t); + + // Q_ENUM + template + typename std::enable_if::Value, Log&>::type + operator<<(T t) { + const QMetaObject* meta = qt_getEnumMetaObject(t); + const char* name = qt_getEnumName(t); + addMetaEnum(typename QFlags::Int(t), meta, name); + return *this; + } + + private: + void addMetaEnum(quint64 value, const QMetaObject* meta, const char* name); + + Logger* m_logger; + LogLevel m_logLevel; + + struct Data { + Data() : m_ts(&m_buffer, QIODevice::WriteOnly) {} + + QString m_buffer; + QTextStream m_ts; + }; + + Data* m_data; + }; + + Log error(); + Log warning(); + Log info(); + Log debug(); + QString sensitive(const QString& input); + private: Logger() {} Logger(Logger const &) = delete; @@ -46,6 +98,9 @@ private: static QString m_logFileName; friend void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); + + // compat with Mozilla logger + QString m_className; }; #endif // LOGGER_H diff --git a/client/mozilla/controllerimpl.h b/client/mozilla/controllerimpl.h new file mode 100644 index 00000000..4804ba3c --- /dev/null +++ b/client/mozilla/controllerimpl.h @@ -0,0 +1,89 @@ +/* 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 CONTROLLERIMPL_H +#define CONTROLLERIMPL_H + +#include +#include +#include + +class Keys; +class Device; +class Server; +class QDateTime; +class IPAddress; +class QHostAddress; + +// This object is allocated when the VPN is about to be activated. +// It's kept alive, basically forever, except in these scenarios, in which it's +// recreated: +// - the user does a logout +// - there is an authentication falure +class ControllerImpl : public QObject { + Q_OBJECT + + public: + ControllerImpl() = default; + + virtual ~ControllerImpl() = default; + + // This method is called to initialize the controller. The initialization + // is completed when the signal "initialized" is emitted. + virtual void initialize(const Device* device, const Keys* keys) = 0; + + // This method is called when the VPN client needs to activate the VPN + // tunnel. It's called only at the end of the initialization process. When + // this method is called, the VPN client is in "connecting" state. This + // state terminates when the "connected" (or the "disconnected") signal is + // received. + virtual void activate(const QJsonObject& config) = 0; + + // This method terminates the VPN tunnel. The VPN client is in + // "disconnecting" state until the "disconnected" signal is received. + virtual void deactivate() = 0; + + // This method is used to retrieve the VPN tunnel status (mainly the number + // of bytes sent and received). It's called always when the VPN tunnel is + // active. + virtual void checkStatus() = 0; + + // This method is used to retrieve the logs from the backend service. Use + // the callback to report logs when available. + virtual void getBackendLogs( + std::function&& callback) = 0; + + // Cleanup the backend logs. + virtual void cleanupBackendLogs() = 0; + + // Whether the controller supports multihop + virtual bool multihopSupported() { return false; } + + virtual bool silentServerSwitchingSupported() const { return true; } + + signals: + // This signal is emitted when the controller is initialized. Note that the + // VPN tunnel can be already active. In this case, "connected" should be set + // to true and the "connectionDate" should be set to the activation date if + // known. + // If "status" is set to false, the backend service is considered unavailable. + void initialized(bool status, bool connected, + const QDateTime& connectionDate); + + // These 2 signals can be dispatched at any time. + void connected(const QString& pubkey, + const QDateTime& connectionTimestamp = QDateTime()); + void disconnected(); + + // This method should be emitted after a checkStatus() call. + // "serverIpv4Gateway" is the current VPN tunnel gateway. + // "deviceIpv4Address" is the address of the VPN client. + // "txBytes" and "rxBytes" contain the number of transmitted and received + // bytes since the last statusUpdated signal. + void statusUpdated(const QString& serverIpv4Gateway, + const QString& deviceIpv4Address, uint64_t txBytes, + uint64_t rxBytes); +}; + +#endif // CONTROLLERIMPL_H diff --git a/client/mozilla/dnspingsender.cpp b/client/mozilla/dnspingsender.cpp new file mode 100644 index 00000000..f690fdaa --- /dev/null +++ b/client/mozilla/dnspingsender.cpp @@ -0,0 +1,117 @@ +/* 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 "dnspingsender.h" + +#include + +#include +#include + +#include "leakdetector.h" +#include "logger.h" + +constexpr const quint16 DNS_PORT = 53; + +// A quick and dirty DNS Header structure definition from RFC1035, +// Section 4.1.1: Header Section format. +struct dnsHeader { + quint16 id; + quint16 flags; + quint16 qdcount; + quint16 ancount; + quint16 nscount; + quint16 arcount; +}; + +// Bit definitions for the DNS flags field. +#define DNS_FLAG_QR 0x8000 +#define DNS_FLAG_OPCODE 0x7800 +#define DNS_FLAG_OPCODE_QUERY (0x0 << 11) +#define DNS_FLAG_OPCODE_IQUERY (0x1 << 11) +#define DNS_FLAG_OPCODE_STATUS (0x2 << 11) +#define DNS_FLAG_AA 0x0400 +#define DNS_FLAG_TC 0x0200 +#define DNS_FLAG_RD 0x0100 +#define DNS_FLAG_RA 0x0080 +#define DNS_FLAG_Z 0x0070 +#define DNS_FLAG_RCODE 0x000F +#define DNS_FLAG_RCODE_NO_ERROR (0x0 << 0) +#define DNS_FLAG_RCODE_FORMAT_ERROR (0x1 << 0) +#define DNS_FLAG_RCODE_SERVER_FAILURE (0x2 << 0) +#define DNS_FLAG_RCODE_NAME_ERROR (0x3 << 0) +#define DNS_FLAG_RCODE_NOT_IMPLEMENTED (0x4 << 0) +#define DNS_FLAG_RCODE_REFUSED (0x5 << 0) + +namespace { +Logger logger("DnsPingSender"); +} + +DnsPingSender::DnsPingSender(const QHostAddress& source, QObject* parent) + : PingSender(parent) { + MZ_COUNT_CTOR(DnsPingSender); + + if (source.isNull()) { + m_socket.bind(); + } else { + m_socket.bind(source); + } + + connect(&m_socket, &QUdpSocket::readyRead, this, &DnsPingSender::readData); +} + +DnsPingSender::~DnsPingSender() { MZ_COUNT_DTOR(DnsPingSender); } + +void DnsPingSender::sendPing(const QHostAddress& dest, quint16 sequence) { + QByteArray packet; + + // Assemble a DNS query header. + struct dnsHeader header; + memset(&header, 0, sizeof(header)); + header.id = qToBigEndian(sequence); + header.flags = qToBigEndian(DNS_FLAG_OPCODE_QUERY); + header.qdcount = qToBigEndian(1); + header.ancount = 0; + header.nscount = 0; + header.arcount = 0; + packet.append(reinterpret_cast(&header), sizeof(header)); + + // Add a query for the root nameserver: {, type A, class IN} + const char query[] = {0x00, 0x00, 0x01, 0x00, 0x01}; + packet.append(query, sizeof(query)); + + // Send the datagram. + m_socket.writeDatagram(packet, dest, DNS_PORT); +} + +void DnsPingSender::readData() { + while (m_socket.hasPendingDatagrams()) { + QNetworkDatagram reply = m_socket.receiveDatagram(); + if (!reply.isValid()) { + break; + } + + // Extract the header from the DNS response. + QByteArray payload = reply.data(); + struct dnsHeader header; + if (payload.length() < static_cast(sizeof(header))) { + logger.debug() << "Received bogus DNS reply: truncated header"; + continue; + } + memcpy(&header, payload.constData(), sizeof(header)); + + // Perfom some checks to ensure this is the reply we were expecting. + quint16 flags = qFromBigEndian(header.flags); + if ((flags & DNS_FLAG_QR) == 0) { + logger.debug() << "Received bogus DNS reply: QR == query"; + continue; + } + if ((flags & DNS_FLAG_OPCODE) != DNS_FLAG_OPCODE_QUERY) { + logger.debug() << "Received bogus DNS reply: OPCODE != query"; + continue; + } + + emit recvPing(qFromBigEndian(header.id)); + } +} diff --git a/client/mozilla/dnspingsender.h b/client/mozilla/dnspingsender.h new file mode 100644 index 00000000..6539dc8d --- /dev/null +++ b/client/mozilla/dnspingsender.h @@ -0,0 +1,29 @@ +/* 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 DNSPINGSENDER_H +#define DNSPINGSENDER_H + +#include + +#include "pingsender.h" + +class DnsPingSender final : public PingSender { + Q_OBJECT + Q_DISABLE_COPY_MOVE(DnsPingSender) + + public: + DnsPingSender(const QHostAddress& source, QObject* parent = nullptr); + ~DnsPingSender(); + + void sendPing(const QHostAddress& dest, quint16 sequence) override; + + private: + void readData(); + + private: + QUdpSocket m_socket; +}; + +#endif // DNSPINGSENDER_H diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp new file mode 100644 index 00000000..8d0c6255 --- /dev/null +++ b/client/mozilla/localsocketcontroller.cpp @@ -0,0 +1,384 @@ +/* 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 "protocols/protocols_defs.h" +#include "localsocketcontroller.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipaddress.h" +#include "leakdetector.h" +#include "logger.h" +#include "models/server.h" + +// How many times do we try to reconnect. +constexpr int MAX_CONNECTION_RETRY = 10; + +// How long do we wait between one try and the next one. +constexpr int CONNECTION_RETRY_TIMER_MSEC = 500; + +namespace { +Logger logger("LocalSocketController"); +} + +LocalSocketController::LocalSocketController() { + MZ_COUNT_CTOR(LocalSocketController); + + m_socket = new QLocalSocket(this); + connect(m_socket, &QLocalSocket::connected, this, + &LocalSocketController::daemonConnected); + connect(m_socket, &QLocalSocket::disconnected, this, + &LocalSocketController::disconnected); + connect(m_socket, &QLocalSocket::errorOccurred, this, + &LocalSocketController::errorOccurred); + connect(m_socket, &QLocalSocket::readyRead, this, + &LocalSocketController::readData); + + m_initializingTimer.setSingleShot(true); + connect(&m_initializingTimer, &QTimer::timeout, this, + &LocalSocketController::initializeInternal); +} + +LocalSocketController::~LocalSocketController() { + MZ_COUNT_DTOR(LocalSocketController); +} + +void LocalSocketController::errorOccurred( + QLocalSocket::LocalSocketError error) { + logger.error() << "Error occurred:" << error; + + if (m_daemonState == eInitializing) { + if (m_initializingRetry++ < MAX_CONNECTION_RETRY) { + m_initializingTimer.start(CONNECTION_RETRY_TIMER_MSEC); + return; + } + + emit initialized(false, false, QDateTime()); + } + + qCritical() << "ControllerError"; + disconnectInternal(); +} + +void LocalSocketController::disconnectInternal() { + // We're still eReady as the Deamon is alive + // and can make a new connection. + m_daemonState = eReady; + m_initializingRetry = 0; + m_initializingTimer.stop(); + emit disconnected(); +} + +void LocalSocketController::initialize(const Device* device, const Keys* keys) { + logger.debug() << "Initializing"; + + Q_UNUSED(device); + Q_UNUSED(keys); + + Q_ASSERT(m_daemonState == eUnknown); + m_initializingRetry = 0; + + initializeInternal(); +} + +void LocalSocketController::initializeInternal() { + m_daemonState = eInitializing; + +#ifdef MZ_WINDOWS + QString path = "\\\\.\\pipe\\amneziavpn"; +#else + QString path = "/var/run/amneziavpn/daemon.socket"; + if (!QFileInfo::exists(path)) { + path = "/tmp/amneziavpn.socket"; + } +#endif + + logger.debug() << "Connecting to:" << path; + m_socket->connectToServer(path); +} + +void LocalSocketController::daemonConnected() { + logger.debug() << "Daemon connected"; + Q_ASSERT(m_daemonState == eInitializing); + checkStatus(); +} + +void LocalSocketController::activate(const QJsonObject &rawConfig) { + + qDebug() << rawConfig; + QJsonObject wgConfig = rawConfig.value("wireguard_config_data").toObject(); + + QJsonObject json; + json.insert("type", "activate"); +// json.insert("hopindex", QJsonValue((double)hop.m_hopindex)); + json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key)); + json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip)); + json.insert("deviceIpv6Address", "dead::1"); + json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key)); + json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key)); + json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName)); +// json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn())); + json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt()); + + json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName)); +// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway())); + json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1)); + + QJsonArray jsAllowedIPAddesses; + + QJsonObject range_ipv4; + range_ipv4.insert("address", "0.0.0.0"); + range_ipv4.insert("range", 0); + range_ipv4.insert("isIpv6", false); + jsAllowedIPAddesses.append(range_ipv4); + + QJsonObject range_ipv6; + range_ipv6.insert("address", "::"); + range_ipv6.insert("range", 0); + range_ipv6.insert("isIpv6", true); + jsAllowedIPAddesses.append(range_ipv6); + + json.insert("allowedIPAddressRanges", jsAllowedIPAddesses); + + + QJsonArray jsExcludedAddresses; + jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName)); + json.insert("excludedAddresses", jsExcludedAddresses); + + +// QJsonArray splitTunnelApps; +// for (const auto& uri : hop.m_vpnDisabledApps) { +// splitTunnelApps.append(QJsonValue(uri)); +// } +// json.insert("vpnDisabledApps", splitTunnelApps); + + write(json); +} + +void LocalSocketController::deactivate() { + logger.debug() << "Deactivating"; + + if (m_daemonState != eReady) { + logger.debug() << "No disconnect, controller is not ready"; + emit disconnected(); + return; + } + + QJsonObject json; + json.insert("type", "deactivate"); + write(json); +} + +void LocalSocketController::checkStatus() { + logger.debug() << "Check status"; + + if (m_daemonState == eReady || m_daemonState == eInitializing) { + Q_ASSERT(m_socket); + + QJsonObject json; + json.insert("type", "status"); + write(json); + } +} + +void LocalSocketController::getBackendLogs( + std::function&& a_callback) { + logger.debug() << "Backend logs"; + + if (m_logCallback) { + m_logCallback(""); + m_logCallback = nullptr; + } + + if (m_daemonState != eReady) { + std::function callback = a_callback; + callback(""); + return; + } + + m_logCallback = std::move(a_callback); + + QJsonObject json; + json.insert("type", "logs"); + write(json); +} + +void LocalSocketController::cleanupBackendLogs() { + logger.debug() << "Cleanup logs"; + + if (m_logCallback) { + m_logCallback(""); + m_logCallback = nullptr; + } + + if (m_daemonState != eReady) { + return; + } + + QJsonObject json; + json.insert("type", "cleanlogs"); + write(json); +} + +void LocalSocketController::readData() { + logger.debug() << "Reading"; + + Q_ASSERT(m_socket); + Q_ASSERT(m_daemonState == eInitializing || m_daemonState == eReady); + QByteArray input = m_socket->readAll(); + m_buffer.append(input); + + while (true) { + int pos = m_buffer.indexOf("\n"); + if (pos == -1) { + break; + } + + QByteArray line = m_buffer.left(pos); + m_buffer.remove(0, pos + 1); + + QByteArray command(line); + command = command.trimmed(); + + if (command.isEmpty()) { + continue; + } + + parseCommand(command); + } +} + +void LocalSocketController::parseCommand(const QByteArray& command) { + QJsonDocument json = QJsonDocument::fromJson(command); + if (!json.isObject()) { + logger.error() << "Invalid JSON - object expected"; + return; + } + + QJsonObject obj = json.object(); + QJsonValue typeValue = obj.value("type"); + if (!typeValue.isString()) { + logger.error() << "Invalid JSON - no type"; + return; + } + QString type = typeValue.toString(); + + logger.debug() << "Parse command:" << type; + + if (m_daemonState == eInitializing && type == "status") { + m_daemonState = eReady; + + QJsonValue connected = obj.value("connected"); + if (!connected.isBool()) { + logger.error() << "Invalid JSON for status - connected expected"; + return; + } + + QDateTime datetime; + if (connected.toBool()) { + QJsonValue date = obj.value("date"); + if (!date.isString()) { + logger.error() << "Invalid JSON for status - date expected"; + return; + } + + datetime = QDateTime::fromString(date.toString()); + if (!datetime.isValid()) { + logger.error() << "Invalid JSON for status - date is invalid"; + return; + } + } + + emit initialized(true, connected.toBool(), datetime); + return; + } + + if (m_daemonState != eReady) { + logger.error() << "Unexpected command"; + return; + } + + if (type == "status") { + QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway"); + if (!serverIpv4Gateway.isString()) { + logger.error() << "Unexpected serverIpv4Gateway value"; + return; + } + + QJsonValue deviceIpv4Address = obj.value("deviceIpv4Address"); + if (!deviceIpv4Address.isString()) { + logger.error() << "Unexpected deviceIpv4Address value"; + return; + } + + QJsonValue txBytes = obj.value("txBytes"); + if (!txBytes.isDouble()) { + logger.error() << "Unexpected txBytes value"; + return; + } + + QJsonValue rxBytes = obj.value("rxBytes"); + if (!rxBytes.isDouble()) { + logger.error() << "Unexpected rxBytes value"; + return; + } + + emit statusUpdated(serverIpv4Gateway.toString(), + deviceIpv4Address.toString(), txBytes.toDouble(), + rxBytes.toDouble()); + return; + } + + if (type == "disconnected") { + disconnectInternal(); + return; + } + + if (type == "connected") { + QJsonValue pubkey = obj.value("pubkey"); + if (!pubkey.isString()) { + logger.error() << "Unexpected pubkey value"; + return; + } + + logger.debug() << "Handshake completed with:" + << pubkey.toString(); + emit connected(pubkey.toString()); + return; + } + + if (type == "backendFailure") { + qCritical() << "backendFailure"; + return; + } + + if (type == "logs") { + // We don't care if we are not waiting for logs. + if (!m_logCallback) { + return; + } + + QJsonValue logs = obj.value("logs"); + m_logCallback(logs.isString() ? logs.toString().replace("|", "\n") + : QString()); + m_logCallback = nullptr; + return; + } + + logger.warning() << "Invalid command received:" << command; +} + +void LocalSocketController::write(const QJsonObject& json) { + Q_ASSERT(m_socket); + m_socket->write(QJsonDocument(json).toJson(QJsonDocument::Compact)); + m_socket->write("\n"); + m_socket->flush(); +} diff --git a/client/mozilla/localsocketcontroller.h b/client/mozilla/localsocketcontroller.h new file mode 100644 index 00000000..f0652e5a --- /dev/null +++ b/client/mozilla/localsocketcontroller.h @@ -0,0 +1,67 @@ +/* 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 LOCALSOCKETCONTROLLER_H +#define LOCALSOCKETCONTROLLER_H + +#include +#include +#include +#include + +#include "controllerimpl.h" + +class QJsonObject; + +class LocalSocketController final : public ControllerImpl { + Q_DISABLE_COPY_MOVE(LocalSocketController) + + public: + LocalSocketController(); + ~LocalSocketController(); + + void initialize(const Device* device, const Keys* keys) override; + + void activate(const QJsonObject& rawConfig) override; + + void deactivate() override; + + void checkStatus() override; + + void getBackendLogs(std::function&& callback) override; + + void cleanupBackendLogs() override; + + bool multihopSupported() override { return true; } + + private: + void initializeInternal(); + void disconnectInternal(); + + void daemonConnected(); + void errorOccurred(QLocalSocket::LocalSocketError socketError); + void readData(); + void parseCommand(const QByteArray& command); + + void write(const QJsonObject& json); + + private: + enum { + eUnknown, + eInitializing, + eReady, + eDisconnected, + } m_daemonState = eUnknown; + + QLocalSocket* m_socket = nullptr; + + QByteArray m_buffer; + + std::function m_logCallback = nullptr; + + QTimer m_initializingTimer; + uint32_t m_initializingRetry = 0; +}; + +#endif // LOCALSOCKETCONTROLLER_H diff --git a/client/mozilla/models/server.cpp b/client/mozilla/models/server.cpp new file mode 100644 index 00000000..a7c73659 --- /dev/null +++ b/client/mozilla/models/server.cpp @@ -0,0 +1,209 @@ +/* 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 "server.h" + +#include +#include +#include +#include +#include + +#include "leakdetector.h" + +Server::Server() { MZ_COUNT_CTOR(Server); } + +Server::Server(const QString& countryCode, const QString& cityName) { + MZ_COUNT_CTOR(Server); + m_countryCode = countryCode; + m_cityName = cityName; +} + +Server::Server(const Server& other) { + MZ_COUNT_CTOR(Server); + *this = other; +} + +Server& Server::operator=(const Server& other) { + if (this == &other) return *this; + + m_hostname = other.m_hostname; + m_ipv4AddrIn = other.m_ipv4AddrIn; + m_ipv4Gateway = other.m_ipv4Gateway; + m_ipv6AddrIn = other.m_ipv6AddrIn; + m_ipv6Gateway = other.m_ipv6Gateway; + m_portRanges = other.m_portRanges; + m_publicKey = other.m_publicKey; + m_weight = other.m_weight; + m_socksName = other.m_socksName; + m_multihopPort = other.m_multihopPort; + m_countryCode = other.m_countryCode; + m_cityName = other.m_cityName; + + return *this; +} + +Server::~Server() { MZ_COUNT_DTOR(Server); } + +bool Server::fromJson(const QJsonObject& obj) { + // Reset. + m_hostname = ""; + + QJsonValue hostname = obj.value("hostname"); + if (!hostname.isString()) { + return false; + } + + QJsonValue ipv4AddrIn = obj.value("ipv4_addr_in"); + if (!ipv4AddrIn.isString()) { + return false; + } + + QJsonValue ipv4Gateway = obj.value("ipv4_gateway"); + if (!ipv4Gateway.isString()) { + return false; + } + + QJsonValue ipv6AddrIn = obj.value("ipv6_addr_in"); + // If this object comes from the IOS migration, the ipv6_addr_in is missing. + + QJsonValue ipv6Gateway = obj.value("ipv6_gateway"); + if (!ipv6Gateway.isString()) { + return false; + } + + QJsonValue publicKey = obj.value("public_key"); + if (!publicKey.isString()) { + return false; + } + + QJsonValue weight = obj.value("weight"); + if (!weight.isDouble()) { + return false; + } + + QJsonValue portRanges = obj.value("port_ranges"); + if (!portRanges.isArray()) { + return false; + } + + // optional properties. + QJsonValue socks5_name = obj.value("socks5_name"); + QJsonValue multihop_port = obj.value("multihop_port"); + + QList> prList; + QJsonArray portRangesArray = portRanges.toArray(); + for (const QJsonValue& portRangeValue : portRangesArray) { + if (!portRangeValue.isArray()) { + return false; + } + + QJsonArray port = portRangeValue.toArray(); + if (port.count() != 2) { + return false; + } + + QJsonValue a = port.at(0); + if (!a.isDouble()) { + return false; + } + + QJsonValue b = port.at(1); + if (!b.isDouble()) { + return false; + } + + prList.append(QPair(a.toInt(), b.toInt())); + } + + m_hostname = hostname.toString(); + m_ipv4AddrIn = ipv4AddrIn.toString(); + m_ipv4Gateway = ipv4Gateway.toString(); + m_ipv6AddrIn = ipv6AddrIn.toString(); + m_ipv6Gateway = ipv6Gateway.toString(); + m_portRanges.swap(prList); + m_publicKey = publicKey.toString(); + m_weight = weight.toInt(); + m_socksName = socks5_name.toString(); + m_multihopPort = multihop_port.toInt(); + + return true; +} + +bool Server::fromMultihop(const Server& exit, const Server& entry) { + m_hostname = exit.m_hostname; + m_ipv4Gateway = exit.m_ipv4Gateway; + m_ipv6Gateway = exit.m_ipv6Gateway; + m_publicKey = exit.m_publicKey; + m_socksName = exit.m_socksName; + m_multihopPort = exit.m_multihopPort; + + m_ipv4AddrIn = entry.m_ipv4AddrIn; + m_ipv6AddrIn = entry.m_ipv6AddrIn; + return forcePort(exit.m_multihopPort); +} + +bool Server::forcePort(uint32_t port) { + m_portRanges.clear(); + m_portRanges.append(QPair(port, port)); + return true; +} + +// static +const Server& Server::weightChooser(const QList& servers) { + static const Server emptyServer; + Q_ASSERT(!emptyServer.initialized()); + if (servers.isEmpty()) { + return emptyServer; + } + + uint32_t weightSum = 0; + + for (const Server& server : servers) { + weightSum += server.weight(); + } + + quint32 r = QRandomGenerator::global()->generate() % (weightSum + 1); + + for (const Server& server : servers) { + if (server.weight() >= r) { + return server; + } + + r -= server.weight(); + } + + // This should not happen. + Q_ASSERT(false); + return emptyServer; +} + +uint32_t Server::choosePort() const { + if (m_portRanges.isEmpty()) { + return 0; + } + + // Count the total number of potential ports. + quint32 length = 0; + for (const QPair& range : m_portRanges) { + Q_ASSERT(range.first <= range.second); + length += range.second - range.first + 1; + } + Q_ASSERT(length < 65536); + Q_ASSERT(length > 0); + + // Pick a port at random. + quint32 r = QRandomGenerator::global()->generate() % length; + quint32 port = 0; + + for (const QPair& range : m_portRanges) { + if (r <= (range.second - range.first)) { + port = r + range.first; + break; + } + r -= (range.second - range.first + 1); + } + Q_ASSERT(port != 0); + return port; +} diff --git a/client/mozilla/models/server.h b/client/mozilla/models/server.h new file mode 100644 index 00000000..04e8faff --- /dev/null +++ b/client/mozilla/models/server.h @@ -0,0 +1,79 @@ +/* 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 SERVER_H +#define SERVER_H + +#include +#include +#include + +class QJsonObject; + +class Server final { + public: + Server(); + Server(const QString& countryCode, const QString& cityName); + Server(const Server& other); + Server& operator=(const Server& other); + ~Server(); + + [[nodiscard]] bool fromJson(const QJsonObject& obj); + bool fromMultihop(const Server& exit, const Server& entry); + + static const Server& weightChooser(const QList& servers); + + bool initialized() const { return !m_hostname.isEmpty(); } + + const QString& hostname() const { return m_hostname; } + + const QString& ipv4AddrIn() const { return m_ipv4AddrIn; } + + const QString& ipv4Gateway() const { return m_ipv4Gateway; } + + const QString& ipv6AddrIn() const { return m_ipv6AddrIn; } + + const QString& ipv6Gateway() const { return m_ipv6Gateway; } + + const QString& publicKey() const { return m_publicKey; } + + const QString& socksName() const { return m_socksName; } + + uint32_t weight() const { return m_weight; } + + uint32_t choosePort() const; + + uint32_t multihopPort() const { return m_multihopPort; } + + const QString& countryCode() const { return m_countryCode; } + + const QString& cityName() const { return m_cityName; } + + bool forcePort(uint32_t port); + + bool operator==(const Server& other) const { + return m_publicKey == other.m_publicKey; + } + // Allow checking against QString, so we can easily search a QList for + // a public key. + bool operator==(const QString& otherPublicKey) const { + return m_publicKey == otherPublicKey; + } + + private: + QString m_hostname; + QString m_ipv4AddrIn; + QString m_ipv4Gateway; + QString m_ipv6AddrIn; + QString m_ipv6Gateway; + QList> m_portRanges; + QString m_publicKey; + QString m_socksName; + uint32_t m_weight = 0; + uint32_t m_multihopPort = 0; + QString m_countryCode; + QString m_cityName; +}; + +#endif // SERVER_H diff --git a/client/mozilla/networkwatcher.cpp b/client/mozilla/networkwatcher.cpp new file mode 100644 index 00000000..a3323fbc --- /dev/null +++ b/client/mozilla/networkwatcher.cpp @@ -0,0 +1,106 @@ +/* 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 "networkwatcher.h" + +#include + +#include "leakdetector.h" +#include "logger.h" +#include "networkwatcherimpl.h" +#include "platforms/dummy/dummynetworkwatcher.h" + +#ifdef MZ_WINDOWS +//# include "platforms/windows/windowsnetworkwatcher.h" +#endif + +#ifdef MZ_LINUX +//# include "platforms/linux/linuxnetworkwatcher.h" +#endif + +#ifdef MZ_MACOS +# include "platforms/macos/macosnetworkwatcher.h" +#endif + +#ifdef MZ_WASM +# include "platforms/wasm/wasmnetworkwatcher.h" +#endif +#ifdef MZ_ANDROID +# include "platforms/android/androidnetworkwatcher.h" +#endif + +#ifdef MZ_IOS +# include "platforms/ios/iosnetworkwatcher.h" +#endif + +// How often we notify the same unsecured network +#ifndef UNIT_TEST +constexpr uint32_t NETWORK_WATCHER_TIMER_MSEC = 20000; +#endif + +namespace { +Logger logger("NetworkWatcher"); +} + +NetworkWatcher::NetworkWatcher() { MZ_COUNT_CTOR(NetworkWatcher); } + +NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); } + +void NetworkWatcher::initialize() { + logger.debug() << "Initialize"; + +#if defined(MZ_WINDOWS) + //m_impl = new WindowsNetworkWatcher(this); +#elif defined(MZ_LINUX) + //m_impl = new LinuxNetworkWatcher(this); +#elif defined(MZ_MACOS) + m_impl = new MacOSNetworkWatcher(this); +#elif defined(MZ_WASM) + m_impl = new WasmNetworkWatcher(this); +#elif defined(MZ_ANDROID) + m_impl = new AndroidNetworkWatcher(this); +#elif defined(MZ_IOS) + m_impl = new IOSNetworkWatcher(this); +#else + m_impl = new DummyNetworkWatcher(this); +#endif + + connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this, + &NetworkWatcher::unsecuredNetwork); + connect(m_impl, &NetworkWatcherImpl::networkChanged, this, + &NetworkWatcher::networkChange); + + m_impl->initialize(); + + //TODO IMPL FOR AMNEZIA +} + +void NetworkWatcher::settingsChanged() { + //TODO IMPL FOR AMNEZIA + + 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(); + } +} + +void NetworkWatcher::unsecuredNetwork(const QString& networkName, + const QString& networkId) { + logger.debug() << "Unsecured network:" << logger.sensitive(networkName) + << "id:" << logger.sensitive(networkId); + + //TODO IMPL FOR AMNEZIA +} + +QString NetworkWatcher::getCurrentTransport() { + auto type = m_impl->getTransportType(); + QMetaEnum metaEnum = QMetaEnum::fromType(); + return QString(metaEnum.valueToKey(type)) + .remove("TransportType_", Qt::CaseSensitive); +} diff --git a/client/mozilla/networkwatcher.h b/client/mozilla/networkwatcher.h new file mode 100644 index 00000000..0c4444c2 --- /dev/null +++ b/client/mozilla/networkwatcher.h @@ -0,0 +1,49 @@ +/* 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 NETWORKWATCHER_H +#define NETWORKWATCHER_H + +#include +#include +#include + +class NetworkWatcherImpl; + +// This class watches for network changes to detect unsecured wifi. +class NetworkWatcher final : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(NetworkWatcher) + + public: + NetworkWatcher(); + ~NetworkWatcher(); + + void initialize(); + + // public for the inspector. + void unsecuredNetwork(const QString& networkName, const QString& networkId); + + QString getCurrentTransport(); + + signals: + void networkChange(); + + private: + void settingsChanged(); + + private: + bool m_active = false; + bool m_reportUnsecuredNetwork = false; + + // Platform-specific implementation. + NetworkWatcherImpl* m_impl = nullptr; + + QMap m_networks; + + // This is used to connect NotificationHandler lazily. + bool m_firstNotification = true; +}; + +#endif // NETWORKWATCHER_H diff --git a/client/mozilla/networkwatcherimpl.h b/client/mozilla/networkwatcherimpl.h new file mode 100644 index 00000000..d156c0db --- /dev/null +++ b/client/mozilla/networkwatcherimpl.h @@ -0,0 +1,54 @@ +/* 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 NETWORKWATCHERIMPL_H +#define NETWORKWATCHERIMPL_H + +#include + +class NetworkWatcherImpl : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(NetworkWatcherImpl) + + public: + NetworkWatcherImpl(QObject* parent) : QObject(parent) {} + + virtual ~NetworkWatcherImpl() = default; + + virtual void initialize() = 0; + + virtual void start() { m_active = true; } + virtual void stop() { m_active = false; } + + bool isActive() const { return m_active; } + + enum TransportType { + TransportType_Unknown = 0, + TransportType_Ethernet = 1, + TransportType_WiFi = 2, + TransportType_Cellular = 3, // In Case the API does not retun the gsm type + TransportType_Other = 4, // I.e USB thethering + TransportType_None = 5 // I.e Airplane Mode or no active network device + }; + Q_ENUM(TransportType); + + // Returns the current type of Network Connection + virtual TransportType getTransportType() = 0; + + signals: + // Fires when the Device Connects to an unsecured Network + void unsecuredNetwork(const QString& networkName, const QString& networkId); + // Fires on when the connected WIFI Changes + // TODO: Only windows-networkwatcher has this, the other plattforms should + // too. + void networkChanged(QString newBSSID); + + // Fired when the Device changed the Type of Transport + void transportChanged(NetworkWatcherImpl::TransportType transportType); + + private: + bool m_active = false; +}; + +#endif // NETWORKWATCHERIMPL_H diff --git a/client/mozilla/pinghelper.cpp b/client/mozilla/pinghelper.cpp new file mode 100644 index 00000000..44920bf6 --- /dev/null +++ b/client/mozilla/pinghelper.cpp @@ -0,0 +1,189 @@ +/* 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 "pinghelper.h" + +#include +#include + +#include "dnspingsender.h" +#include "leakdetector.h" +#include "logger.h" +#include "pingsender.h" +#include "pingsenderfactory.h" + +// Any X seconds, a new ping. +constexpr uint32_t PING_TIMEOUT_SEC = 1; + +// Maximum window size for ping statistics. +constexpr int PING_STATS_WINDOW = 32; + +namespace { +Logger logger("PingHelper"); +} + +PingHelper::PingHelper() { + MZ_COUNT_CTOR(PingHelper); + + m_sequence = 0; + m_pingData.resize(PING_STATS_WINDOW); + + connect(&m_pingTimer, &QTimer::timeout, this, &PingHelper::nextPing); +} + +PingHelper::~PingHelper() { MZ_COUNT_DTOR(PingHelper); } + +void PingHelper::start(const QString& serverIpv4Gateway, + const QString& deviceIpv4Address) { + logger.debug() << "PingHelper activated for server:" + << logger.sensitive(serverIpv4Gateway); + + m_gateway = QHostAddress(serverIpv4Gateway); + m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0)); + m_pingSender = PingSenderFactory::create(m_source, this); + + // Some platforms require root access to send and receive ICMP pings. If + // we happen to be on one of these unlucky devices, create a DnsPingSender + // instead. + if (!m_pingSender->isValid()) { + delete m_pingSender; + m_pingSender = new DnsPingSender(m_source, this); + } + + connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived, + Qt::QueuedConnection); + connect(m_pingSender, &PingSender::criticalPingError, this, + []() { logger.info() << "Encountered Unrecoverable ping error"; }); + + // Reset the ping statistics + m_sequence = 0; + for (int i = 0; i < PING_STATS_WINDOW; i++) { + m_pingData[i].timestamp = -1; + m_pingData[i].latency = -1; + m_pingData[i].sequence = 0; + } + + m_pingTimer.start(PING_TIMEOUT_SEC * 1000); +} + +void PingHelper::stop() { + logger.debug() << "PingHelper deactivated"; + + if (m_pingSender) { + delete m_pingSender; + m_pingSender = nullptr; + } + + m_pingTimer.stop(); +} + +void PingHelper::nextPing() { +#ifdef MZ_DEBUG + logger.debug() << "Sending ping seq:" << m_sequence; +#endif + + // The ICMP sequence number is used to match replies with their originating + // request, and serves as an index into the circular buffer. Overflows of + // the sequence number acceptable. + int index = m_sequence % PING_STATS_WINDOW; + m_pingData[index].timestamp = QDateTime::currentMSecsSinceEpoch(); + m_pingData[index].latency = -1; + m_pingData[index].sequence = m_sequence; + m_pingSender->sendPing(m_gateway, m_sequence); + + m_sequence++; +} + +void PingHelper::pingReceived(quint16 sequence) { + int index = sequence % PING_STATS_WINDOW; + if (m_pingData[index].sequence == sequence) { + qint64 sendTime = m_pingData[index].timestamp; + m_pingData[index].latency = QDateTime::currentMSecsSinceEpoch() - sendTime; + emit pingSentAndReceived(m_pingData[index].latency); +#ifdef MZ_DEBUG + logger.debug() << "Ping answer received seq:" << sequence + << "avg:" << latency() + << "loss:" << QString("%1%").arg(loss() * 100.0) + << "stddev:" << stddev(); +#endif + } +} + +uint PingHelper::latency() const { + int recvCount = 0; + qint64 totalMsec = 0; + + for (const PingSendData& data : m_pingData) { + if (data.latency < 0) { + continue; + } + recvCount++; + totalMsec += data.latency; + } + + if (recvCount <= 0) { + return 0.0; + } + + // Add half the denominator to produce nearest-integer rounding. + totalMsec += recvCount / 2; + return static_cast(totalMsec / recvCount); +} + +uint PingHelper::stddev() const { + int recvCount = 0; + qint64 totalVariance = 0; + uint average = PingHelper::latency(); + + for (const PingSendData& data : m_pingData) { + if (data.latency < 0) { + continue; + } + recvCount++; + totalVariance += (average - data.latency) * (average - data.latency); + } + + if (recvCount <= 0) { + return 0.0; + } + + return std::sqrt((double)totalVariance / recvCount); +} + +uint PingHelper::maximum() const { + uint maxRtt = 0; + + for (const PingSendData& data : m_pingData) { + if (data.latency < 0) { + continue; + } + if (data.latency > maxRtt && + data.latency < std::numeric_limits::max()) { + maxRtt = static_cast(data.latency); + } + } + return maxRtt; +} + +double PingHelper::loss() const { + int sendCount = 0; + int recvCount = 0; + // Don't count pings that are possibly still in flight as losses. + qint64 sendBefore = + QDateTime::currentMSecsSinceEpoch() - (PING_TIMEOUT_SEC * 1000); + + for (const PingSendData& data : m_pingData) { + if (data.latency >= 0) { + recvCount++; + sendCount++; + } else if ((data.timestamp > 0) && (data.timestamp < sendBefore)) { + sendCount++; + } + } + + if (sendCount <= 0) { + return 0.0; + } + return (double)(sendCount - recvCount) / PING_STATS_WINDOW; +} diff --git a/client/mozilla/pinghelper.h b/client/mozilla/pinghelper.h new file mode 100644 index 00000000..38e3a298 --- /dev/null +++ b/client/mozilla/pinghelper.h @@ -0,0 +1,64 @@ +/* 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 PINGHELPER_H +#define PINGHELPER_H + +#include +#include +#include +#include +#include + +class PingSender; + +class PingHelper final : public QObject { + private: + Q_OBJECT + Q_DISABLE_COPY_MOVE(PingHelper) + + public: + PingHelper(); + ~PingHelper(); + + void start(const QString& serverIpv4Gateway, + const QString& deviceIpv4Address); + + void stop(); + uint latency() const; + uint stddev() const; + uint maximum() const; + double loss() const; + + signals: + void pingSentAndReceived(qint64 msec); + + private: + void nextPing(); + + void pingReceived(quint16 sequence); + + private: + QHostAddress m_gateway; + QHostAddress m_source; + quint16 m_sequence = 0; + + class PingSendData { + public: + PingSendData() { + timestamp = -1; + latency = -1; + sequence = 0; + } + qint64 timestamp; + qint64 latency; + quint16 sequence; + }; + QVector m_pingData; + + QTimer m_pingTimer; + PingSender* m_pingSender = nullptr; +}; + +#endif // PINGHELPER_H diff --git a/client/mozilla/pingsender.cpp b/client/mozilla/pingsender.cpp new file mode 100644 index 00000000..87acd932 --- /dev/null +++ b/client/mozilla/pingsender.cpp @@ -0,0 +1,48 @@ +/* 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 "pingsender.h" + +#include "logger.h" + +namespace { +Logger logger("PingSender"); +} + +quint16 PingSender::inetChecksum(const void* data, size_t len) { + int nleft, sum; + quint16* w; + union { + quint16 us; + quint8 uc[2]; + } last; + quint16 answer; + + nleft = static_cast(len); + sum = 0; + w = (quint16*)data; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), we add + * sequential 16 bit words to it, and at the end, fold back all the + * carry bits from the top 16 bits into the lower 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) { + last.uc[0] = *(quint8*)w; + last.uc[1] = 0; + sum += last.us; + } + + /* add back carry outs from top 16 bits to low 16 bits */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return (answer); +} diff --git a/client/mozilla/pingsender.h b/client/mozilla/pingsender.h new file mode 100644 index 00000000..06c89405 --- /dev/null +++ b/client/mozilla/pingsender.h @@ -0,0 +1,31 @@ +/* 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 PINGSENDER_H +#define PINGSENDER_H + +#include +#include +#include + +class PingSender : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(PingSender) + + public: + PingSender(QObject* parent = nullptr) : QObject(parent) {} + virtual ~PingSender() = default; + + virtual bool isValid() { return true; }; + + virtual void sendPing(const QHostAddress& destination, quint16 sequence) = 0; + + static quint16 inetChecksum(const void* data, size_t length); + + signals: + void recvPing(quint16 sequence); + void criticalPingError(); +}; + +#endif // PINGSENDER_H diff --git a/client/mozilla/pingsenderfactory.cpp b/client/mozilla/pingsenderfactory.cpp new file mode 100644 index 00000000..6e376f0d --- /dev/null +++ b/client/mozilla/pingsenderfactory.cpp @@ -0,0 +1,32 @@ +/* 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 "pingsenderfactory.h" + +#if defined(MZ_LINUX) || defined(MZ_ANDROID) +//# include "platforms/linux/linuxpingsender.h" +#elif defined(MZ_MACOS) || defined(MZ_IOS) +# include "platforms/macos/macospingsender.h" +#elif defined(MZ_WINDOWS) +// #include "platforms/windows/windowspingsender.h" +#elif defined(MZ_DUMMY) || defined(UNIT_TEST) +# include "platforms/dummy/dummypingsender.h" +#else +# error "Unsupported platform" +#endif + +PingSender* PingSenderFactory::create(const QHostAddress& source, + QObject* parent) { +#if defined(MZ_LINUX) || defined(MZ_ANDROID) + return nullptr; + //return new LinuxPingSender(source, parent); +#elif defined(MZ_MACOS) || defined(MZ_IOS) + return new MacOSPingSender(source, parent); +#elif defined(MZ_WINDOWS) + return nullptr; + //return new WindowsPingSender(source, parent); +#else + return new DummyPingSender(source, parent); +#endif +} diff --git a/client/mozilla/pingsenderfactory.h b/client/mozilla/pingsenderfactory.h new file mode 100644 index 00000000..7482d36d --- /dev/null +++ b/client/mozilla/pingsenderfactory.h @@ -0,0 +1,18 @@ +/* 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 PINGSENDERFACTORY_H +#define PINGSENDERFACTORY_H + +class PingSender; +class QHostAddress; +class QObject; + +class PingSenderFactory final { + public: + PingSenderFactory() = delete; + static PingSender* create(const QHostAddress& source, QObject* parent); +}; + +#endif // PINGSENDERFACTORY_H diff --git a/client/mozilla/shared/ipaddress.cpp b/client/mozilla/shared/ipaddress.cpp new file mode 100644 index 00000000..7c337755 --- /dev/null +++ b/client/mozilla/shared/ipaddress.cpp @@ -0,0 +1,286 @@ +/* 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 "ipaddress.h" + +#include + +#include "leakdetector.h" + +IPAddress::IPAddress() { MZ_COUNT_CTOR(IPAddress); } + +IPAddress::IPAddress(const QString& ip) { + MZ_COUNT_CTOR(IPAddress); + if (ip.contains("/")) { + QPair p = QHostAddress::parseSubnet(ip); + m_address = p.first; + m_prefixLength = p.second; + } else { + m_address = QHostAddress(ip); + m_prefixLength = 999999; + } + + if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + if (m_prefixLength >= 32) { + m_prefixLength = 32; + } + } else if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + if (m_prefixLength >= 128) { + m_prefixLength = 128; + } + } else { + Q_ASSERT(false); + } +} + +IPAddress::IPAddress(const IPAddress& other) { + MZ_COUNT_CTOR(IPAddress); + *this = other; +} + +IPAddress& IPAddress::operator=(const IPAddress& other) { + if (this == &other) return *this; + + m_address = other.m_address; + m_prefixLength = other.m_prefixLength; + + return *this; +} + +IPAddress::IPAddress(const QHostAddress& address) : m_address(address) { + MZ_COUNT_CTOR(IPAddress); + + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + m_prefixLength = 32; + } else { + Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol); + m_prefixLength = 128; + } +} + +IPAddress::IPAddress(const QHostAddress& address, int prefixLength) + : m_address(address), m_prefixLength(prefixLength) { + MZ_COUNT_CTOR(IPAddress); + + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + Q_ASSERT(prefixLength >= 0 && prefixLength <= 32); + } else { + Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol); + Q_ASSERT(prefixLength >= 0 && prefixLength <= 128); + } +} + +IPAddress::~IPAddress() { MZ_COUNT_DTOR(IPAddress); } + +QAbstractSocket::NetworkLayerProtocol IPAddress::type() const { + return m_address.protocol(); +} + +QHostAddress IPAddress::netmask() const { + if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR rawNetmask = {0}; + Q_ASSERT(m_prefixLength <= 128); + memset(&rawNetmask, 0xff, m_prefixLength / 8); + if (m_prefixLength % 8) { + rawNetmask[m_prefixLength / 8] = 0xFF ^ (0xFF >> (m_prefixLength % 8)); + } + return QHostAddress(rawNetmask); + } else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + quint32 rawNetmask = 0xffffffff; + Q_ASSERT(m_prefixLength <= 32); + if (m_prefixLength < 32) { + rawNetmask ^= (0xffffffff >> m_prefixLength); + } + return QHostAddress(rawNetmask); + } else { + return QHostAddress(); + } +} + +QHostAddress IPAddress::hostmask() const { + if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR rawHostmask = {0}; + int offset = (m_prefixLength + 7) / 8; + Q_ASSERT(m_prefixLength <= 128); + memset(&rawHostmask[offset], 0xff, sizeof(rawHostmask) - offset); + if (m_prefixLength % 8) { + rawHostmask[m_prefixLength / 8] = 0xFF >> (m_prefixLength % 8); + } + return QHostAddress(rawHostmask); + } else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + if (m_prefixLength < 32) { + return QHostAddress(0xffffffff >> m_prefixLength); + } else { + quint32 zero = 0; + return QHostAddress(zero); + } + } else { + return QHostAddress(); + } +} + +QHostAddress IPAddress::broadcastAddress() const { + if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR rawAddress = m_address.toIPv6Address(); + int offset = (m_prefixLength + 7) / 8; + memset(&rawAddress[offset], 0xff, sizeof(rawAddress) - offset); + if (m_prefixLength % 8) { + rawAddress[m_prefixLength / 8] |= 0xFF >> (m_prefixLength % 8); + } + return QHostAddress(rawAddress); + } else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + quint32 rawAddress = m_address.toIPv4Address(); + if (m_prefixLength < 32) { + rawAddress |= (0xffffffff >> m_prefixLength); + } + return QHostAddress(rawAddress); + } else { + return QHostAddress(); + } +} + +bool IPAddress::overlaps(const IPAddress& other) const { + if (m_prefixLength < other.m_prefixLength) { + return contains(other.m_address); + } else { + return other.contains(m_address); + } +} + +bool IPAddress::contains(const QHostAddress& address) const { + if (address.protocol() != m_address.protocol()) { + return false; + } + if (m_prefixLength == 0) { + return true; + } + + if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR a = m_address.toIPv6Address(); + Q_IPV6ADDR b = address.toIPv6Address(); + int bytes = m_prefixLength / 8; + if (bytes > 0) { + if (memcmp(&a, &b, bytes) != 0) { + return false; + } + } + + if (m_prefixLength % 8) { + quint8 diff = (a[bytes] ^ b[bytes]) >> (8 - m_prefixLength % 8); + return (diff == 0); + } + + return true; + } + + if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + quint32 diff = m_address.toIPv4Address() ^ address.toIPv4Address(); + if (m_prefixLength < 32) { + diff >>= (32 - m_prefixLength); + } + return (diff == 0); + } + + return false; +} + +bool IPAddress::operator==(const IPAddress& other) const { + return m_address == other.m_address && m_prefixLength == other.m_prefixLength; +} + +bool IPAddress::subnetOf(const IPAddress& other) const { + if (other.m_address.protocol() != m_address.protocol()) { + return false; + } + if (m_prefixLength < other.m_prefixLength) { + return false; + } + + return other.contains(m_address); +} + +QList IPAddress::subnets() const { + QList list; + + if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + if (m_prefixLength >= 32) { + list.append(*this); + return list; + } + + quint32 rawAddress = m_address.toIPv4Address(); + list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1)); + + rawAddress |= (0x80000000 >> m_prefixLength); + list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1)); + + return list; + } + + Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol); + + if (m_prefixLength >= 128) { + list.append(*this); + return list; + } + + Q_IPV6ADDR rawAddress = m_address.toIPv6Address(); + list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1)); + + rawAddress[m_prefixLength / 8] |= (0x80 >> (m_prefixLength % 8)); + list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1)); + + return list; +} + +// static +QList IPAddress::excludeAddresses( + const QList& sourceList, const QList& excludeList) { + QList results = sourceList; + + for (const IPAddress& exclude : excludeList) { + QList newResults; + + for (const IPAddress& ip : results) { + if (!ip.overlaps(exclude)) { + newResults.append(ip); + } else if (exclude.subnetOf(ip) && exclude != ip) { + QList range = ip.excludeAddresses(exclude); + newResults.append(range); + } + } + + results = newResults; + } + + return results; +} + +QList IPAddress::excludeAddresses(const IPAddress& ip) const { + QList sn = subnets(); + Q_ASSERT(sn.length() >= 2); + + QList result; + while (sn[0] != ip && sn[1] != ip) { + if (ip.subnetOf(sn[0])) { + result.append(sn[1]); + sn = sn[0].subnets(); + } else if (ip.subnetOf(sn[1])) { + result.append(sn[0]); + sn = sn[1].subnets(); + } else { + Q_ASSERT(false); + } + } + + if (sn[0] == ip) { + result.append(sn[1]); + } else if (sn[1] == ip) { + result.append(sn[0]); + } else { + Q_ASSERT(false); + } + + return result; +} diff --git a/client/platforms/ios/ipaddress.h b/client/mozilla/shared/ipaddress.h similarity index 80% rename from client/platforms/ios/ipaddress.h rename to client/mozilla/shared/ipaddress.h index f41ecd69..05b04de2 100644 --- a/client/platforms/ios/ipaddress.h +++ b/client/mozilla/shared/ipaddress.h @@ -9,11 +9,13 @@ class IPAddress final { public: - static IPAddress create(const QString& ip); static QList excludeAddresses(const QList& sourceList, const QList& excludeList); IPAddress(); + IPAddress(const QString& ip); + IPAddress(const QHostAddress& address); + IPAddress(const QHostAddress& address, int prefixLength); IPAddress(const IPAddress& other); IPAddress& operator=(const IPAddress& other); ~IPAddress(); @@ -24,9 +26,9 @@ class IPAddress final { const QHostAddress& address() const { return m_address; } int prefixLength() const { return m_prefixLength; } - const QHostAddress& netmask() const { return m_netmask; } - const QHostAddress& hostmask() const { return m_hostmask; } - const QHostAddress& broadcastAddress() const { return m_broadcastAddress; } + QHostAddress netmask() const; + QHostAddress hostmask() const; + QHostAddress broadcastAddress() const; bool overlaps(const IPAddress& other) const; @@ -43,17 +45,9 @@ class IPAddress final { QAbstractSocket::NetworkLayerProtocol type() const; - private: - IPAddress(const QHostAddress& address); - IPAddress(const QHostAddress& address, int prefixLength); - private: QHostAddress m_address; int m_prefixLength; - - QHostAddress m_netmask; - QHostAddress m_hostmask; - QHostAddress m_broadcastAddress; }; #endif // IPADDRESS_H diff --git a/client/platforms/linux/leakdetector.cpp b/client/mozilla/shared/leakdetector.cpp similarity index 91% rename from client/platforms/linux/leakdetector.cpp rename to client/mozilla/shared/leakdetector.cpp index 7a092a8a..6afcecb7 100644 --- a/client/platforms/linux/leakdetector.cpp +++ b/client/mozilla/shared/leakdetector.cpp @@ -9,23 +9,23 @@ #include #include -#ifdef MVPN_DEBUG +#ifdef MZ_DEBUG static QMutex s_leakDetector; QHash> s_leaks; #endif LeakDetector::LeakDetector() { -#ifndef MVPN_DEBUG +#ifndef MZ_DEBUG qFatal("LeakDetector _must_ be created in debug builds only!"); #endif } LeakDetector::~LeakDetector() { -#ifdef MVPN_DEBUG +#ifdef MZ_DEBUG QTextStream out(stderr); - out << "== Mozilla VPN - Leak report ===================" << Qt::endl; + out << "== MZ - Leak report ===================" << Qt::endl; bool hasLeaks = false; for (auto i = s_leaks.begin(); i != s_leaks.end(); ++i) { @@ -49,7 +49,7 @@ LeakDetector::~LeakDetector() { #endif } -#ifdef MVPN_DEBUG +#ifdef MZ_DEBUG void LeakDetector::logCtor(void* ptr, const char* typeName, uint32_t size) { QMutexLocker lock(&s_leakDetector); diff --git a/client/platforms/linux/leakdetector.h b/client/mozilla/shared/leakdetector.h similarity index 82% rename from client/platforms/linux/leakdetector.h rename to client/mozilla/shared/leakdetector.h index 3016137c..a3f6dd50 100644 --- a/client/platforms/linux/leakdetector.h +++ b/client/mozilla/shared/leakdetector.h @@ -7,15 +7,15 @@ #include -#ifdef MVPN_DEBUG -# define MVPN_COUNT_CTOR(_type) \ +#ifdef MZ_DEBUG +# define MZ_COUNT_CTOR(_type) \ do { \ static_assert(std::is_class<_type>(), \ "Token '" #_type "' is not a class type."); \ LeakDetector::logCtor((void*)this, #_type, sizeof(*this)); \ } while (0) -# define MVPN_COUNT_DTOR(_type) \ +# define MZ_COUNT_DTOR(_type) \ do { \ static_assert(std::is_class<_type>(), \ "Token '" #_type "' is not a class type."); \ @@ -23,8 +23,8 @@ } while (0) #else -# define MVPN_COUNT_CTOR(_type) -# define MVPN_COUNT_DTOR(_type) +# define MZ_COUNT_CTOR(_type) +# define MZ_COUNT_DTOR(_type) #endif class LeakDetector { @@ -32,7 +32,7 @@ class LeakDetector { LeakDetector(); ~LeakDetector(); -#ifdef MVPN_DEBUG +#ifdef MZ_DEBUG static void logCtor(void* ptr, const char* typeName, uint32_t size); static void logDtor(void* ptr, const char* typeName, uint32_t size); #endif diff --git a/client/platforms/ios/iosdatamigration.h b/client/mozilla/shared/loglevel.h similarity index 55% rename from client/platforms/ios/iosdatamigration.h rename to client/mozilla/shared/loglevel.h index 2c76792f..857e2035 100644 --- a/client/platforms/ios/iosdatamigration.h +++ b/client/mozilla/shared/loglevel.h @@ -2,14 +2,15 @@ * 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 IOSDATAMIGRATION_H -#define IOSDATAMIGRATION_H +#ifndef LOGLEVEL_H +#define LOGLEVEL_H -#include - -class IOSDataMigration final { - public: - static void migrate(); +enum LogLevel { + Trace = 0, + Debug, + Info, + Warning, + Error, }; -#endif // IOSDATAMIGRATION_H +#endif // LOGLEVEL_H diff --git a/client/mozilla/shared/signalhandler.cpp b/client/mozilla/shared/signalhandler.cpp new file mode 100644 index 00000000..6b8125ae --- /dev/null +++ b/client/mozilla/shared/signalhandler.cpp @@ -0,0 +1,76 @@ +/* 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 "signalhandler.h" + +#include +#include +#include + +#include "logger.h" + +namespace { + +Logger logger("SignalHandler"); + +int s_signalpipe = -1; + +} // namespace + +SignalHandler::SignalHandler() { + Q_ASSERT(s_signalpipe < 0); + + int quitSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; + + sigset_t mask; + sigemptyset(&mask); + for (auto sig : quitSignals) { + sigaddset(&mask, sig); + } + + if (pipe(m_pipefds) != 0) { + logger.error() << "Unable to create signal wakeup pipe"; + return; + } + fcntl(m_pipefds[0], F_SETFL, fcntl(m_pipefds[0], F_GETFL) | O_NONBLOCK); + s_signalpipe = m_pipefds[1]; + m_notifier = new QSocketNotifier(m_pipefds[0], QSocketNotifier::Read, this); + connect(m_notifier, &QSocketNotifier::activated, this, + &SignalHandler::pipeReadReady); + + struct sigaction sa; + sa.sa_handler = SignalHandler::saHandler; + sa.sa_mask = mask; + sa.sa_flags = 0; + + for (auto sig : quitSignals) { + sigaction(sig, &sa, nullptr); + } +} + +SignalHandler::~SignalHandler() { + s_signalpipe = -1; + if (m_pipefds[0] >= 0) { + close(m_pipefds[0]); + } + if (m_pipefds[1] >= 1) { + close(m_pipefds[1]); + } +} + +void SignalHandler::pipeReadReady() { + int signal; + if (read(m_pipefds[0], &signal, sizeof(signal)) == sizeof(signal)) { + logger.debug() << "Signal" << signal; + emit quitRequested(); + } +} + +void SignalHandler::saHandler(int signal) { + if (s_signalpipe >= 0) { + if (write(s_signalpipe, &signal, sizeof(signal)) != sizeof(signal)) { + logger.warning() << "Unable to write in the pipe"; + } + } +} diff --git a/client/mozilla/shared/signalhandler.h b/client/mozilla/shared/signalhandler.h new file mode 100644 index 00000000..8f0bc24f --- /dev/null +++ b/client/mozilla/shared/signalhandler.h @@ -0,0 +1,31 @@ +/* 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 SIGNALHANDLER_H +#define SIGNALHANDLER_H + +#include +#include + +class SignalHandler final : public QObject { + Q_OBJECT + + public: + SignalHandler(); + ~SignalHandler(); + + private slots: + void pipeReadReady(); + + private: + static void saHandler(int signal); + + int m_pipefds[2] = {-1, -1}; + QSocketNotifier* m_notifier = nullptr; + + signals: + void quitRequested(); +}; + +#endif // SIGNALHANDLER_H diff --git a/client/platforms/dummy/dummyapplistprovider.cpp b/client/platforms/dummy/dummyapplistprovider.cpp new file mode 100644 index 00000000..efcab6f6 --- /dev/null +++ b/client/platforms/dummy/dummyapplistprovider.cpp @@ -0,0 +1,29 @@ +/* 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 "dummyapplistprovider.h" + +#include "leakdetector.h" + +DummyAppListProvider::DummyAppListProvider(QObject* parent) + : AppListProvider(parent) { + MZ_COUNT_CTOR(DummyAppListProvider); +} + +DummyAppListProvider::~DummyAppListProvider() { + MZ_COUNT_DTOR(DummyAppListProvider); +} + +void DummyAppListProvider::getApplicationList() { + QMap appList; + appList["com.example.one"] = "a Example App 1"; + appList["com.example.two"] = "B Example App 2"; + appList["org.example.one"] = "c Example App 3"; + appList["org.example.two"] = "D Example App 4"; + appList["com.example.a"] = "e Example App 5"; + appList["com.example.b"] = "F Example App 6"; + appList["org.example.c"] = "g Example App 7"; + appList["org.example.d"] = "H Example App 8"; + emit newAppList(appList); +} diff --git a/client/platforms/dummy/dummyapplistprovider.h b/client/platforms/dummy/dummyapplistprovider.h new file mode 100644 index 00000000..931c08e0 --- /dev/null +++ b/client/platforms/dummy/dummyapplistprovider.h @@ -0,0 +1,20 @@ +/* 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 DUMMYAPPLISTPROVIDER_H +#define DUMMYAPPLISTPROVIDER_H + +#include + +#include "applistprovider.h" + +class DummyAppListProvider : public AppListProvider { + Q_OBJECT + public: + DummyAppListProvider(QObject* parent); + ~DummyAppListProvider(); + void getApplicationList() override; +}; + +#endif // DUMMYAPPLISTPROVIDER_H diff --git a/client/platforms/dummy/dummynetworkwatcher.cpp b/client/platforms/dummy/dummynetworkwatcher.cpp new file mode 100644 index 00000000..b4291494 --- /dev/null +++ b/client/platforms/dummy/dummynetworkwatcher.cpp @@ -0,0 +1,18 @@ +/* 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 "dummynetworkwatcher.h" + +#include "leakdetector.h" + +DummyNetworkWatcher::DummyNetworkWatcher(QObject* parent) + : NetworkWatcherImpl(parent) { + MZ_COUNT_CTOR(DummyNetworkWatcher); +} + +DummyNetworkWatcher::~DummyNetworkWatcher() { + MZ_COUNT_DTOR(DummyNetworkWatcher); +} + +void DummyNetworkWatcher::initialize() {} diff --git a/client/platforms/dummy/dummynetworkwatcher.h b/client/platforms/dummy/dummynetworkwatcher.h new file mode 100644 index 00000000..4e6d2a66 --- /dev/null +++ b/client/platforms/dummy/dummynetworkwatcher.h @@ -0,0 +1,22 @@ +/* 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 DUMMYNETWORKWATCHER_H +#define DUMMYNETWORKWATCHER_H + +#include "networkwatcherimpl.h" + +class DummyNetworkWatcher final : public NetworkWatcherImpl { + public: + DummyNetworkWatcher(QObject* parent); + ~DummyNetworkWatcher(); + + void initialize() override; + + NetworkWatcherImpl::TransportType getTransportType() override { + return TransportType_Other; + }; +}; + +#endif // DUMMYNETWORKWATCHER_H diff --git a/client/platforms/dummy/dummypingsender.cpp b/client/platforms/dummy/dummypingsender.cpp new file mode 100644 index 00000000..8f04d495 --- /dev/null +++ b/client/platforms/dummy/dummypingsender.cpp @@ -0,0 +1,25 @@ +/* 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 "dummypingsender.h" + +#include "leakdetector.h" +#include "logger.h" + +namespace { +Logger logger("DummyPingSender"); +} + +DummyPingSender::DummyPingSender(const QHostAddress& source, QObject* parent) + : PingSender(parent) { + MZ_COUNT_CTOR(DummyPingSender); + Q_UNUSED(source); +} + +DummyPingSender::~DummyPingSender() { MZ_COUNT_DTOR(DummyPingSender); } + +void DummyPingSender::sendPing(const QHostAddress& dest, quint16 sequence) { + logger.debug() << "Dummy ping to:" << dest.toString(); + emit recvPing(sequence); +} diff --git a/client/platforms/dummy/dummypingsender.h b/client/platforms/dummy/dummypingsender.h new file mode 100644 index 00000000..6cab488c --- /dev/null +++ b/client/platforms/dummy/dummypingsender.h @@ -0,0 +1,21 @@ +/* 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 DUMMYPINGSENDER_H +#define DUMMYPINGSENDER_H + +#include "pingsender.h" + +class DummyPingSender final : public PingSender { + Q_OBJECT + Q_DISABLE_COPY_MOVE(DummyPingSender) + + public: + DummyPingSender(const QHostAddress& source, QObject* parent = nullptr); + ~DummyPingSender(); + + void sendPing(const QHostAddress& dest, quint16 sequence) override; +}; + +#endif // DUMMYPINGSENDER_H diff --git a/client/platforms/ios/bigint.h b/client/platforms/ios/bigint.h deleted file mode 100644 index ce45cf69..00000000 --- a/client/platforms/ios/bigint.h +++ /dev/null @@ -1,127 +0,0 @@ -/* 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 BIGINT_H -#define BIGINT_H - -#include - -// This BigInt implementation is meant to be used for IPv6 addresses. It -// doesn't support dynamic resize: when the max size is reached, the value -// overflows. If you need to change the size, use `resize()`. - -class BigInt final { - public: - explicit BigInt(uint8_t bytes) { - m_value.resize(bytes); - memset(m_value.data(), 0, bytes); - } - - BigInt(const BigInt& other) { m_value = other.m_value; } - - const uint8_t* value() const { return m_value.data(); } - - uint8_t size() const { return m_value.size(); } - - // Assign operator. - - BigInt& operator=(const BigInt& other) { - m_value = other.m_value; - return *this; - } - - // Comparison operators. - - bool operator==(const BigInt& other) const { - return m_value == other.m_value; - } - - bool operator!=(const BigInt& other) const { return !(*this == other); } - - bool operator<(const BigInt& other) const { return cmp(other) < 0; } - - bool operator>(const BigInt& other) const { return cmp(other) > 0; } - - bool operator<=(const BigInt& other) const { return cmp(other) <= 0; } - - bool operator>=(const BigInt& other) const { return cmp(other) >= 0; } - - // math operators (only some of them are implemented) - - BigInt& operator++() { - for (int i = size() - 1; i >= 0; --i) { - if (m_value[i] < UINT8_MAX) { - ++m_value[i]; - return *this; - } - m_value[i] = 0; - } - - // overflow - memset(m_value.data(), 0, size()); - return *this; - } - - BigInt& operator+=(const BigInt& other) { - Q_ASSERT(other.size() == size()); - - uint8_t carry = 0; - for (int i = m_value.size() - 1; i >= 0; --i) { - uint16_t total = carry + m_value[i] + other.m_value[i]; - m_value[i] = (uint8_t)(total & UINT8_MAX); - carry = (uint8_t)((total & 0xFF00) >> 8); - } - - return *this; - } - - // Shift operators - - BigInt operator>>(int shift) { - BigInt x(size()); - x = *this; - - for (int i = 0; i < shift; i++) { - BigInt a(size()); - a = x; - - a.m_value[size() - 1] = x.m_value[size() - 1] >> 1; - for (int j = size() - 2; j >= 0; j--) { - a.m_value[j] = x.m_value[j] >> 1; - if ((x.m_value[j] & 1) != 0) { - a.m_value[j + 1] |= 128; // Set most significant bit or a uint8_t - } - } - - x = a; - } - - return x; - } - - void setValueAt(uint8_t value, uint8_t pos) { - Q_ASSERT(pos < size()); - m_value[pos] = value; - } - - uint8_t valueAt(uint8_t pos) const { - Q_ASSERT(size() > pos); - return m_value[pos]; - } - - private: - int cmp(const BigInt& other) const { - Q_ASSERT(size() == other.size()); - for (int i = 0; i < size(); i++) { - int diff = (m_value[i] - other.m_value[i]); - if (diff != 0) return diff; - } - return 0; - } - - private: - QVector m_value; -}; - -#endif // BIGINT_H diff --git a/client/platforms/ios/bigintipv6addr.h b/client/platforms/ios/bigintipv6addr.h deleted file mode 100644 index a277de6d..00000000 --- a/client/platforms/ios/bigintipv6addr.h +++ /dev/null @@ -1,86 +0,0 @@ -/* 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 BIGINTIPV6ADDR_H -#define BIGINTIPV6ADDR_H - -#include "bigint.h" - -#include - -class BigIntIPv6Addr final { - public: - BigIntIPv6Addr() : m_value(16) {} - - explicit BigIntIPv6Addr(const Q_IPV6ADDR& a) : m_value(16) { - for (int i = 0; i < 16; ++i) m_value.setValueAt(a[i], i); - } - - BigIntIPv6Addr(const BigIntIPv6Addr& other) : m_value(16) { *this = other; } - - Q_IPV6ADDR value() const { - Q_IPV6ADDR addr; - for (int i = 0; i < 16; ++i) addr[i] = m_value.valueAt(i); - return addr; - } - - // Assign operator. - - BigIntIPv6Addr& operator=(const BigIntIPv6Addr& other) { - m_value = other.m_value; - return *this; - } - - // Comparison operators. - - bool operator==(const BigIntIPv6Addr& other) const { - return m_value == other.m_value; - } - - bool operator!=(const BigIntIPv6Addr& other) const { - return m_value != other.m_value; - } - - bool operator<(const BigIntIPv6Addr& other) const { - return m_value < other.m_value; - } - - bool operator>(const BigIntIPv6Addr& other) const { - return m_value > other.m_value; - } - - bool operator<=(const BigIntIPv6Addr& other) const { - return m_value <= other.m_value; - } - - bool operator>=(const BigIntIPv6Addr& other) const { - return m_value >= other.m_value; - } - - // math operators (only some of them are implemented) - - BigIntIPv6Addr& operator++() { - ++m_value; - return *this; - } - - BigIntIPv6Addr& operator+=(const BigIntIPv6Addr& b) { - m_value += b.m_value; - return *this; - } - - // Shift operators - - BigIntIPv6Addr operator>>(int shift) { - BigIntIPv6Addr x; - - x.m_value = m_value >> shift; - return x; - } - - private: - BigInt m_value; -}; - -#endif // BIGINTIPV6ADDR_H diff --git a/client/platforms/ios/iosadjusthelper.h b/client/platforms/ios/iosadjusthelper.h deleted file mode 100644 index 25c52fbd..00000000 --- a/client/platforms/ios/iosadjusthelper.h +++ /dev/null @@ -1,16 +0,0 @@ -/* 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 IOSADJUSTHELPER_H -#define IOSADJUSTHELPER_H - -#include - -class IOSAdjustHelper final { - public: - static void initialize(); - static void trackEvent(const QString& eventToken); -}; - -#endif // IOSADJUSTHELPER_H diff --git a/client/platforms/ios/iosadjusthelper.mm b/client/platforms/ios/iosadjusthelper.mm deleted file mode 100644 index 8133525c..00000000 --- a/client/platforms/ios/iosadjusthelper.mm +++ /dev/null @@ -1,37 +0,0 @@ -/* 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 "iosadjusthelper.h" -#include "logger.h" -#include "constants.h" - -#import - -namespace { - -Logger logger(LOG_IOS, "IOSAdjustHelper"); - -} // namespace - -void IOSAdjustHelper::initialize() { - - NSString *adjustToken = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"ADJUST_SDK_TOKEN"]; - - if(adjustToken.length) { - NSString *environment = Constants::inProduction() ? ADJEnvironmentProduction : ADJEnvironmentSandbox; - ADJConfig *adjustConfig = [ADJConfig configWithAppToken:adjustToken - environment:environment]; - [adjustConfig setLogLevel:ADJLogLevelDebug]; - [Adjust appDidLaunch:adjustConfig]; - } -} - -void IOSAdjustHelper::trackEvent(const QString& eventToken) { - NSString *adjustToken = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"ADJUST_SDK_TOKEN"]; - - if(adjustToken.length) { - ADJEvent *event = [ADJEvent eventWithEventToken:eventToken.toNSString()]; - [Adjust trackEvent:event]; - } -} diff --git a/client/platforms/ios/iosauthenticationlistener.h b/client/platforms/ios/iosauthenticationlistener.h deleted file mode 100644 index 02b83211..00000000 --- a/client/platforms/ios/iosauthenticationlistener.h +++ /dev/null @@ -1,23 +0,0 @@ -/* 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 IOSAUTHENTICATIONLISTENER_H -#define IOSAUTHENTICATIONLISTENER_H - -#include "authenticationlistener.h" - -#include - -class IOSAuthenticationListener final : public AuthenticationListener { - Q_DISABLE_COPY_MOVE(IOSAuthenticationListener) - - public: - IOSAuthenticationListener(QObject* parent); - ~IOSAuthenticationListener(); - - void start(const QString& codeChallenge, const QString& codeChallengeMethod, - const QString& emailAddress) override; -}; - -#endif // IOSAUTHENTICATIONLISTENER_H diff --git a/client/platforms/ios/iosauthenticationlistener.mm b/client/platforms/ios/iosauthenticationlistener.mm deleted file mode 100644 index ec5d2847..00000000 --- a/client/platforms/ios/iosauthenticationlistener.mm +++ /dev/null @@ -1,139 +0,0 @@ -/* 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 "iosauthenticationlistener.h" -#include "leakdetector.h" -#include "logger.h" -#include "mozillavpn.h" -#include "qmlengineholder.h" - -#include -#include -#include -#include -#include -#include -#include - -#import -#import - -namespace { - -Logger logger({LOG_IOS, LOG_MAIN}, "IOSAuthenticationListener"); - -ASWebAuthenticationSession* session = nullptr; - -} // namespace - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 -@interface ContextProvider : NSObject { - UIView* m_view; -} -@end - -@implementation ContextProvider - -- (id)initWithUIView:(UIView*)uiView { - self = [super init]; - if (self) { - m_view = uiView; - } - return self; -} - -# pragma mark - ASWebAuthenticationPresentationContextProviding -- (nonnull ASPresentationAnchor)presentationAnchorForWebAuthenticationSession: - (nonnull ASWebAuthenticationSession*)session API_AVAILABLE(ios(13.0)) { - return m_view.window; -} - -@end -#endif - -IOSAuthenticationListener::IOSAuthenticationListener(QObject* parent) - : AuthenticationListener(parent) { - MVPN_COUNT_CTOR(IOSAuthenticationListener); -} - -IOSAuthenticationListener::~IOSAuthenticationListener() { - MVPN_COUNT_DTOR(IOSAuthenticationListener); -} - -void IOSAuthenticationListener::start(const QString& codeChallenge, - const QString& codeChallengeMethod, - const QString& emailAddress) { - logger.debug() << "IOSAuthenticationListener initialize"; - - QUrl url(createAuthenticationUrl(AmneziaVPN::AuthenticationInBrowser, codeChallenge, - codeChallengeMethod, emailAddress)); - QUrlQuery query(url.query()); - query.addQueryItem("platform", "ios"); - url.setQuery(query); - -#ifdef QT_DEBUG - logger.debug() << "Authentication URL:" << url.toString(); -#endif - - if (session) { - [session dealloc]; - session = nullptr; - } - - session = [[ASWebAuthenticationSession alloc] - initWithURL:url.toNSURL() - callbackURLScheme:@"mozilla-vpn" - completionHandler:^(NSURL* _Nullable callbackURL, NSError* _Nullable error) { - [session dealloc]; - session = nullptr; - - if (error) { - logger.error() << "Authentication failed:" - << QString::fromNSString([error localizedDescription]); - logger.error() << "Code:" << [error code]; - logger.error() << "Suggestion:" - << QString::fromNSString([error localizedRecoverySuggestion]); - logger.error() << "Reason:" << QString::fromNSString([error localizedFailureReason]); - - if ([error code] == ASWebAuthenticationSessionErrorCodeCanceledLogin) { - emit abortedByUser(); - } else { - emit failed(ErrorHandler::RemoteServiceError); - } - - return; - } - - QUrl callbackUrl = QUrl::fromNSURL(callbackURL); - logger.debug() << "Authentication completed"; - - Q_ASSERT(callbackUrl.hasQuery()); - - QUrlQuery callbackUrlQuery(callbackUrl.query()); - Q_ASSERT(callbackUrlQuery.hasQueryItem("code")); - QString code = callbackUrlQuery.queryItemValue("code"); - emit completed(code); - }]; - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 - QObject* rootObject = QmlEngineHolder::instance()->engine()->rootObjects().first(); - QWindow* window = qobject_cast(rootObject); - Q_ASSERT(window); - - UIView* view = static_cast( - QGuiApplication::platformNativeInterface()->nativeResourceForWindow("uiview", window)); - - if (@available(iOS 13, *)) { - session.presentationContextProvider = [[ContextProvider alloc] initWithUIView:view]; - } -#endif - - if (![session start]) { - [session dealloc]; - session = nullptr; - - logger.error() << "Authentication failed: session doesn't start."; - emit failed(ErrorHandler::RemoteServiceError); - } -} diff --git a/client/platforms/ios/ioscontroller.h b/client/platforms/ios/ioscontroller.h deleted file mode 100644 index da18d5fe..00000000 --- a/client/platforms/ios/ioscontroller.h +++ /dev/null @@ -1,39 +0,0 @@ -/* 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 IOSCONTROLLER_H -#define IOSCONTROLLER_H - -#include "vpnprotocol.h" - -#include - -class IOSVPNProtocol final : public VpnProtocol { - Q_DISABLE_COPY_MOVE(IOSVPNProtocol) - - public: - IOSController(); - ~IOSController(); - - void initialize(const Device* device, const Keys* keys) override; - - void activate(const QList& serverList, const Device* device, - const Keys* keys, - const QList& allowedIPAddressRanges, - const QList& vpnDisabledApps, - const QHostAddress& dnsServer, Reason reason) override; - - void deactivate(Reason reason) override; - - void checkStatus() override; - - void getBackendLogs(std::function&& callback) override; - - void cleanupBackendLogs() override; - - private: - bool m_checkingStatus = false; -}; - -#endif // IOSCONTROLLER_H diff --git a/client/platforms/ios/ioscontroller.mm b/client/platforms/ios/ioscontroller.mm deleted file mode 100644 index a90f24eb..00000000 --- a/client/platforms/ios/ioscontroller.mm +++ /dev/null @@ -1,240 +0,0 @@ -/* 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 "ioscontroller.h" -#include "Mozilla_VPN-Swift.h" -#include "device.h" -#include "ipaddressrange.h" -#include "keys.h" -#include "leakdetector.h" -#include "logger.h" -#include "mozillavpn.h" -#include "server.h" -#include "settingsholder.h" - -#include -#include -#include - -namespace { - -Logger logger({LOG_IOS, LOG_CONTROLLER}, "IOSController"); - -// Our Swift singleton. -IOSControllerImpl* impl = nullptr; - -} // namespace - -IOSController::IOSController() { - MVPN_COUNT_CTOR(IOSController); - - logger.debug() << "created"; - - Q_ASSERT(!impl); -} - -IOSController::~IOSController() { - MVPN_COUNT_DTOR(IOSController); - - logger.debug() << "deallocated"; - - if (impl) { - [impl dealloc]; - impl = nullptr; - } -} - -void IOSController::initialize(const Device* device, const Keys* keys) { - Q_ASSERT(!impl); - Q_UNUSED(device); - - logger.debug() << "Initializing Swift Controller"; - - static bool creating = false; - // No nested creation! - Q_ASSERT(creating == false); - creating = true; - - QByteArray key = QByteArray::fromBase64(keys->privateKey().toLocal8Bit()); - - impl = [[IOSControllerImpl alloc] initWithBundleID:@VPN_NE_BUNDLEID - privateKey:key.toNSData() - deviceIpv4Address:device->ipv4Address().toNSString() - deviceIpv6Address:device->ipv6Address().toNSString() - closure:^(ConnectionState state, NSDate* date) { - logger.debug() << "Creation completed with connection state:" << state; - creating = false; - - switch (state) { - case ConnectionStateError: { - [impl dealloc]; - impl = nullptr; - emit initialized(false, false, QDateTime()); - return; - } - case ConnectionStateConnected: { - Q_ASSERT(date); - QDateTime qtDate(QDateTime::fromNSDate(date)); - emit initialized(true, true, qtDate); - return; - } - case ConnectionStateDisconnected: - // Just in case we are connecting, let's call disconnect. - [impl disconnect]; - emit initialized(true, false, QDateTime()); - return; - } - } - callback:^(BOOL a_connected) { - logger.debug() << "State changed: " << a_connected; - if (a_connected) { - emit connected(); - return; - } - - emit disconnected(); - }]; -} - -void IOSController::activate(const QList& serverList, const Device* device, - const Keys* keys, const QList& allowedIPAddressRanges, - const QList& vpnDisabledApps, const QHostAddress& dnsServer, - Reason reason) { - Q_UNUSED(device); - Q_UNUSED(keys); - - Q_ASSERT(serverList.length() == 1); - const Server& server = serverList[0]; - - // This feature is not supported on macos/ios yet. - Q_ASSERT(vpnDisabledApps.isEmpty()); - - logger.debug() << "IOSController activating" << server.hostname(); - - if (!impl) { - logger.error() << "Controller not correctly initialized"; - emit disconnected(); - return; - } - - NSMutableArray* allowedIPAddressRangesNS = - [NSMutableArray arrayWithCapacity:allowedIPAddressRanges.length()]; - for (const IPAddressRange& i : allowedIPAddressRanges) { - VPNIPAddressRange* range = - [[VPNIPAddressRange alloc] initWithAddress:i.ipAddress().toNSString() - networkPrefixLength:i.range() - isIpv6:i.type() == IPAddressRange::IPv6]; - [allowedIPAddressRangesNS addObject:[range autorelease]]; - } - - [impl connectWithDnsServer:dnsServer.toString().toNSString() - serverIpv6Gateway:server.ipv6Gateway().toNSString() - serverPublicKey:server.publicKey().toNSString() - serverIpv4AddrIn:server.ipv4AddrIn().toNSString() - serverPort:server.choosePort() - allowedIPAddressRanges:allowedIPAddressRangesNS - ipv6Enabled:SettingsHolder::instance()->ipv6Enabled() - reason:reason - failureCallback:^() { - logger.error() << "IOSSWiftController - connection failed"; - emit disconnected(); - }]; -} - -void IOSController::deactivate(Reason reason) { - logger.debug() << "IOSController deactivated"; - - if (reason != ReasonNone) { - logger.debug() << "We do not need to disable the VPN for switching or connection check."; - emit disconnected(); - return; - } - - if (!impl) { - logger.error() << "Controller not correctly initialized"; - emit disconnected(); - return; - } - - [impl disconnect]; -} - -void IOSController::checkStatus() { - logger.debug() << "Checking status"; - - if (m_checkingStatus) { - logger.warning() << "We are still waiting for the previous status."; - return; - } - - if (!impl) { - logger.error() << "Controller not correctly initialized"; - return; - } - - m_checkingStatus = true; - - [impl checkStatusWithCallback:^(NSString* serverIpv4Gateway, NSString* deviceIpv4Address, - NSString* configString) { - QString config = QString::fromNSString(configString); - - m_checkingStatus = false; - - if (config.isEmpty()) { - return; - } - - uint64_t txBytes = 0; - uint64_t rxBytes = 0; - - QStringList lines = config.split("\n"); - for (const QString& line : lines) { - if (line.startsWith("tx_bytes=")) { - txBytes = line.split("=")[1].toULongLong(); - } else if (line.startsWith("rx_bytes=")) { - rxBytes = line.split("=")[1].toULongLong(); - } - - if (txBytes && rxBytes) { - break; - } - } - - logger.debug() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway) - << "DeviceIpv4Address:" << QString::fromNSString(deviceIpv4Address) - << "RxBytes:" << rxBytes << "TxBytes:" << txBytes; - emit statusUpdated(QString::fromNSString(serverIpv4Gateway), - QString::fromNSString(deviceIpv4Address), txBytes, rxBytes); - }]; -} - -void IOSController::getBackendLogs(std::function&& a_callback) { - std::function callback = std::move(a_callback); - - QString groupId(GROUP_ID); - NSURL* groupPath = [[NSFileManager defaultManager] - containerURLForSecurityApplicationGroupIdentifier:groupId.toNSString()]; - - NSURL* path = [groupPath URLByAppendingPathComponent:@"networkextension.log"]; - - QFile file(QString::fromNSString([path path])); - if (!file.open(QIODevice::ReadOnly)) { - callback("Network extension log file missing or unreadable."); - return; - } - - QByteArray content = file.readAll(); - callback(content); -} - -void IOSController::cleanupBackendLogs() { - QString groupId(GROUP_ID); - NSURL* groupPath = [[NSFileManager defaultManager] - containerURLForSecurityApplicationGroupIdentifier:groupId.toNSString()]; - - NSURL* path = [groupPath URLByAppendingPathComponent:@"networkextension.log"]; - - QFile file(QString::fromNSString([path path])); - file.remove(); -} diff --git a/client/platforms/ios/ioscontroller.swift b/client/platforms/ios/ioscontroller.swift deleted file mode 100644 index 352cc82a..00000000 --- a/client/platforms/ios/ioscontroller.swift +++ /dev/null @@ -1,288 +0,0 @@ -/* 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 Foundation -import NetworkExtension - -let vpnName = "Mozilla VPN" -var vpnBundleID = ""; - -@objc class VPNIPAddressRange : NSObject { - public var address: NSString = "" - public var networkPrefixLength: UInt8 = 0 - public var isIpv6: Bool = false - - @objc init(address: NSString, networkPrefixLength: UInt8, isIpv6: Bool) { - super.init() - - self.address = address - self.networkPrefixLength = networkPrefixLength - self.isIpv6 = isIpv6 - } -} - -public class IOSControllerImpl : NSObject { - - private var tunnel: NETunnelProviderManager? = nil - private var stateChangeCallback: ((Bool) -> Void?)? = nil - private var privateKey : PrivateKey? = nil - private var deviceIpv4Address: String? = nil - private var deviceIpv6Address: String? = nil - - @objc enum ConnectionState: Int { case Error, Connected, Disconnected } - - @objc init(bundleID: String, privateKey: Data, deviceIpv4Address: String, deviceIpv6Address: String, closure: @escaping (ConnectionState, Date?) -> Void, callback: @escaping (Bool) -> Void) { - super.init() - - Logger.configureGlobal(tagged: "APP", withFilePath: "") - - vpnBundleID = bundleID; - precondition(!vpnBundleID.isEmpty) - - stateChangeCallback = callback - self.privateKey = PrivateKey(rawValue: privateKey) - self.deviceIpv4Address = deviceIpv4Address - self.deviceIpv6Address = deviceIpv6Address - - NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil) - - NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in - if let error = error { - Logger.global?.log(message: "Loading from preference failed: \(error)") - closure(ConnectionState.Error, nil) - return - } - - if self == nil { - Logger.global?.log(message: "We are shutting down.") - return - } - - let nsManagers = managers ?? [] - Logger.global?.log(message: "We have received \(nsManagers.count) managers.") - - let tunnel = nsManagers.first(where: IOSControllerImpl.isOurManager(_:)) - if tunnel == nil { - Logger.global?.log(message: "Creating the tunnel") - self!.tunnel = NETunnelProviderManager() - closure(ConnectionState.Disconnected, nil) - return - } - - Logger.global?.log(message: "Tunnel already exists") - - self!.tunnel = tunnel - if tunnel?.connection.status == .connected { - closure(ConnectionState.Connected, tunnel?.connection.connectedDate) - } else { - closure(ConnectionState.Disconnected, nil) - } - } - } - - @objc private func vpnStatusDidChange(notification: Notification) { - guard let session = (notification.object as? NETunnelProviderSession), tunnel?.connection == session else { return } - - switch session.status { - case .connected: - Logger.global?.log(message: "STATE CHANGED: connected") - case .connecting: - Logger.global?.log(message: "STATE CHANGED: connecting") - case .disconnected: - Logger.global?.log(message: "STATE CHANGED: disconnected") - case .disconnecting: - Logger.global?.log(message: "STATE CHANGED: disconnecting") - case .invalid: - Logger.global?.log(message: "STATE CHANGED: invalid") - case .reasserting: - Logger.global?.log(message: "STATE CHANGED: reasserting") - default: - Logger.global?.log(message: "STATE CHANGED: unknown status") - } - - // We care about "unknown" state changes. - if (session.status != .connected && session.status != .disconnected) { - return - } - - stateChangeCallback?(session.status == .connected) - } - - private static func isOurManager(_ manager: NETunnelProviderManager) -> Bool { - guard - let proto = manager.protocolConfiguration, - let tunnelProto = proto as? NETunnelProviderProtocol - else { - Logger.global?.log(message: "Ignoring manager because the proto is invalid.") - return false - } - - if (tunnelProto.providerBundleIdentifier == nil) { - Logger.global?.log(message: "Ignoring manager because the bundle identifier is null.") - return false - } - - if (tunnelProto.providerBundleIdentifier != vpnBundleID) { - Logger.global?.log(message: "Ignoring manager because the bundle identifier doesn't match.") - return false; - } - - Logger.global?.log(message: "Found the manager with the correct bundle identifier: \(tunnelProto.providerBundleIdentifier!)") - return true - } - - @objc func connect(dnsServer: String, serverIpv6Gateway: String, serverPublicKey: String, serverIpv4AddrIn: String, serverPort: Int, allowedIPAddressRanges: Array, ipv6Enabled: Bool, reason: Int, failureCallback: @escaping () -> Void) { - Logger.global?.log(message: "Connecting") - assert(tunnel != nil) - - // Let's remove the previous config if it exists. - (tunnel!.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference() - - let keyData = PublicKey(base64Key: serverPublicKey)! - let dnsServerIP = IPv4Address(dnsServer) - let ipv6GatewayIP = IPv6Address(serverIpv6Gateway) - - var peerConfiguration = PeerConfiguration(publicKey: keyData) - peerConfiguration.endpoint = Endpoint(from: serverIpv4AddrIn + ":\(serverPort )") - peerConfiguration.allowedIPs = [] - - allowedIPAddressRanges.forEach { - if (!$0.isIpv6) { - peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv4Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength)) - } else if (ipv6Enabled) { - peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv6Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength)) - } - } - - var peerConfigurations: [PeerConfiguration] = [] - peerConfigurations.append(peerConfiguration) - - var interface = InterfaceConfiguration(privateKey: privateKey!) - - if let ipv4Address = IPAddressRange(from: deviceIpv4Address!), - let ipv6Address = IPAddressRange(from: deviceIpv6Address!) { - interface.addresses = [ipv4Address] - if (ipv6Enabled) { - interface.addresses.append(ipv6Address) - } - } - interface.dns = [ DNSServer(address: dnsServerIP!)] - - if (ipv6Enabled) { - interface.dns.append(DNSServer(address: ipv6GatewayIP!)) - } - - let config = TunnelConfiguration(name: vpnName, interface: interface, peers: peerConfigurations) - - self.configureTunnel(config: config, reason: reason, failureCallback: failureCallback) - } - - func configureTunnel(config: TunnelConfiguration, reason: Int, failureCallback: @escaping () -> Void) { - let proto = NETunnelProviderProtocol(tunnelConfiguration: config) - proto!.providerBundleIdentifier = vpnBundleID - - tunnel!.protocolConfiguration = proto - tunnel!.localizedDescription = vpnName - tunnel!.isEnabled = true - - tunnel!.saveToPreferences { [unowned self] saveError in - if let error = saveError { - Logger.global?.log(message: "Connect Tunnel Save Error: \(error)") - failureCallback() - return - } - - Logger.global?.log(message: "Saving the tunnel succeeded") - - self.tunnel!.loadFromPreferences { error in - if let error = error { - Logger.global?.log(message: "Connect Tunnel Load Error: \(error)") - failureCallback() - return - } - - Logger.global?.log(message: "Loading the tunnel succeeded") - - do { - if (reason == 1 /* ReasonSwitching */) { - let settings = config.asWgQuickConfig() - let settingsData = settings.data(using: .utf8)! - try (self.tunnel!.connection as? NETunnelProviderSession)? - .sendProviderMessage(settingsData) { data in - guard let data = data, - let configString = String(data: data, encoding: .utf8) - else { - Logger.global?.log(message: "Failed to convert response to string") - return - } - } - } else { - try (self.tunnel!.connection as? NETunnelProviderSession)?.startTunnel() - } - } catch let error { - Logger.global?.log(message: "Something went wrong: \(error)") - failureCallback() - return - } - } - } - } - - @objc func disconnect() { - Logger.global?.log(message: "Disconnecting") - assert(tunnel != nil) - (tunnel!.connection as? NETunnelProviderSession)?.stopTunnel() - } - - @objc func checkStatus(callback: @escaping (String, String, String) -> Void) { - assert(tunnel != nil) - - let proto = tunnel!.protocolConfiguration as? NETunnelProviderProtocol - if proto == nil { - callback("", "", "") - return - } - - let tunnelConfiguration = proto?.asTunnelConfiguration() - if tunnelConfiguration == nil { - callback("", "", "") - return - } - - let serverIpv4Gateway = tunnelConfiguration?.interface.dns[0].address - if serverIpv4Gateway == nil { - callback("", "", "") - return - } - - let deviceIpv4Address = tunnelConfiguration?.interface.addresses[0].address - if deviceIpv4Address == nil { - callback("", "", "") - return - } - - guard let session = tunnel?.connection as? NETunnelProviderSession - else { - callback("", "", "") - return - } - - do { - try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in - guard let data = data, - let configString = String(data: data, encoding: .utf8) - else { - Logger.global?.log(message: "Failed to convert data to string") - callback("", "", "") - return - } - - callback("\(serverIpv4Gateway!)", "\(deviceIpv4Address!)", configString) - } - } catch { - Logger.global?.log(message: "Failed to retrieve data from session") - callback("", "", "") - } - } -} diff --git a/client/platforms/ios/iosdatamigration.mm b/client/platforms/ios/iosdatamigration.mm deleted file mode 100644 index 8342bb19..00000000 --- a/client/platforms/ios/iosdatamigration.mm +++ /dev/null @@ -1,172 +0,0 @@ -/* 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 "iosdatamigration.h" -#include "device.h" -#include "logger.h" -#include "mozillavpn.h" -#include "user.h" - -#include -#include -#include -#include -#include - -#import - -namespace { -Logger logger(LOG_IOS, "IOSDataMigration"); - -void migrateUserDefaultData() { - AmneziaVPN* vpn = AmneziaVPN::instance(); - Q_ASSERT(vpn); - - NSUserDefaults* sud = [NSUserDefaults standardUserDefaults]; - if (!sud) { - return; - } - - NSData* userData = [sud dataForKey:@"user"]; - if (userData) { - QByteArray json = QByteArray::fromNSData(userData); - if (!json.isEmpty()) { - logger.debug() << "User data to be migrated"; - vpn->accountChecked(json); - } - } - - NSData* deviceData = [sud dataForKey:@"device"]; - if (deviceData) { - QByteArray json = QByteArray::fromNSData(deviceData); - logger.debug() << "Device data to be migrated"; - // Nothing has to be done here because the device data is part of the user data. - } - - NSData* serversData = [sud dataForKey:@"vpnServers"]; - if (serversData) { - QByteArray json = QByteArray::fromNSData(serversData); - if (!json.isEmpty()) { - logger.debug() << "Server list data to be migrated"; - - // We need to wrap the server list in a object to make it similar to the REST API response. - QJsonDocument serverList = QJsonDocument::fromJson(json); - if (!serverList.isArray()) { - logger.error() << "Server list should be an array!"; - return; - } - - QJsonObject countriesObj; - countriesObj.insert("countries", QJsonValue(serverList.array())); - - QJsonDocument doc; - doc.setObject(countriesObj); - if (!vpn->setServerList(doc.toJson())) { - logger.error() << "Server list cannot be imported"; - return; - } - } - } - - NSData* selectedCityData = [sud dataForKey:@"selectedCity"]; - if (selectedCityData) { - QByteArray json = QByteArray::fromNSData(selectedCityData); - logger.debug() << "SelectedCity data to be migrated" << json; - // Nothing has to be done here because the device data is part of the user data. - - QJsonDocument doc = QJsonDocument::fromJson(json); - if (!doc.isObject()) { - logger.error() << "SelectedCity should be an object"; - return; - } - - QJsonObject obj = doc.object(); - QJsonValue code = obj.value("flagCode"); - if (!code.isString()) { - logger.error() << "SelectedCity code should be a string"; - return; - } - - QJsonValue name = obj.value("code"); - if (!name.isString()) { - logger.error() << "SelectedCity name should be a string"; - return; - } - - ServerData serverData; - if (vpn->serverCountryModel()->pickIfExists(code.toString(), name.toString(), serverData)) { - logger.debug() << "ServerCity found"; - serverData.writeSettings(); - } - } -} - -void migrateKeychainData() { - NSData* service = [@"org.mozilla.guardian.credentials" dataUsingEncoding:NSUTF8StringEncoding]; - - NSMutableDictionary* query = [[NSMutableDictionary alloc] init]; - - [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; - [query setObject:service forKey:(id)kSecAttrService]; - [query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; - [query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; - - NSData* dataNS = NULL; - OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)(void*)&dataNS); - [query release]; - - if (status != noErr) { - logger.error() << "No credentials found"; - return; - } - - QByteArray data = QByteArray::fromNSData(dataNS); - logger.debug() << "Credentials:" << logger.sensitive(data); - - QJsonDocument json = QJsonDocument::fromJson(data); - if (!json.isObject()) { - logger.error() << "JSON object expected"; - return; - } - - QJsonObject obj = json.object(); - QJsonValue deviceKeyValue = obj.value("deviceKeys"); - if (!deviceKeyValue.isObject()) { - logger.error() << "JSON object should have a deviceKeys object"; - return; - } - - QJsonObject deviceKeyObj = deviceKeyValue.toObject(); - QJsonValue publicKey = deviceKeyObj.value("publicKey"); - if (!publicKey.isString()) { - logger.error() << "JSON deviceKey object should contain a publicKey value as string"; - return; - } - - QJsonValue privateKey = deviceKeyObj.value("privateKey"); - if (!privateKey.isString()) { - logger.error() << "JSON deviceKey object should contain a privateKey value as string"; - return; - } - - QJsonValue token = obj.value("verificationToken"); - if (!token.isString()) { - logger.error() << "JSON object should contain a verificationToken value s string"; - return; - } - - AmneziaVPN::instance()->deviceAdded(Device::currentDeviceName(), publicKey.toString(), - privateKey.toString()); - - AmneziaVPN::instance()->setToken(token.toString()); -} -} - -// static -void IOSDataMigration::migrate() { - logger.debug() << "IOS Data Migration"; - - migrateKeychainData(); - migrateUserDefaultData(); -} diff --git a/client/platforms/ios/iosiaphandler.mm b/client/platforms/ios/iosiaphandler.mm deleted file mode 100644 index 84b647f7..00000000 --- a/client/platforms/ios/iosiaphandler.mm +++ /dev/null @@ -1,369 +0,0 @@ -/* 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 "platforms/ios/iosiaphandler.h" -#include "constants.h" -#include "iosutils.h" -#include "leakdetector.h" -#include "logger.h" -#include "mozillavpn.h" -#include "networkrequest.h" -#include "settingsholder.h" - -#include -#include -#include -#include -#include -#include - -#import -#import - -namespace { -Logger logger(LOG_IAP, "IOSIAPHandler"); -} // namespace - -@interface IOSIAPHandlerDelegate - : NSObject { - IOSIAPHandler* m_handler; -} -@end - -@implementation IOSIAPHandlerDelegate - -- (id)initWithObject:(IOSIAPHandler*)handler { - self = [super init]; - if (self) { - m_handler = handler; - } - return self; -} - -- (void)productsRequest:(nonnull SKProductsRequest*)request - didReceiveResponse:(nonnull SKProductsResponse*)response { - logger.debug() << "Registration completed"; - - if (response.invalidProductIdentifiers) { - NSArray* products = response.invalidProductIdentifiers; - logger.error() << "Registration failure" << [products count]; - - for (unsigned long i = 0, count = [products count]; i < count; ++i) { - NSString* identifier = [products objectAtIndex:i]; - QMetaObject::invokeMethod(m_handler, "unknownProductRegistered", Qt::QueuedConnection, - Q_ARG(QString, QString::fromNSString(identifier))); - } - } - - NSArray* products = response.products; - if (products) { - logger.debug() << "Products registered" << [products count]; - - for (unsigned long i = 0, count = [products count]; i < count; ++i) { - SKProduct* product = [[products objectAtIndex:i] retain]; - QMetaObject::invokeMethod(m_handler, "productRegistered", Qt::QueuedConnection, - Q_ARG(void*, product)); - } - } - - QMetaObject::invokeMethod(m_handler, "productsRegistrationCompleted", Qt::QueuedConnection); - - [request release]; -} - -- (void)paymentQueue:(nonnull SKPaymentQueue*)queue - updatedTransactions:(nonnull NSArray*)transactions { - logger.debug() << "payment queue:" << [transactions count]; - - QStringList completedTransactionIds; - bool failedTransactions = false; - bool canceledTransactions = false; - bool completedTransactions = false; - - for (SKPaymentTransaction* transaction in transactions) { - switch (transaction.transactionState) { - case SKPaymentTransactionStateFailed: - logger.error() << "transaction failed"; - - if (transaction.error.code == SKErrorPaymentCancelled) { - canceledTransactions = true; - } else { - failedTransactions = true; - } - break; - - case SKPaymentTransactionStateRestored: - [[fallthrough]]; - case SKPaymentTransactionStatePurchased: { - QString identifier = QString::fromNSString(transaction.transactionIdentifier); - QDateTime date = QDateTime::fromNSDate(transaction.transactionDate); - logger.debug() << "transaction purchased - identifier: " << identifier - << "- date:" << date.toString(); - - if (transaction.transactionState == SKPaymentTransactionStateRestored) { - SKPaymentTransaction* originalTransaction = transaction.originalTransaction; - if (originalTransaction) { - QString originalIdentifier = - QString::fromNSString(originalTransaction.transactionIdentifier); - QDateTime originalDate = QDateTime::fromNSDate(originalTransaction.transactionDate); - logger.debug() << "original transaction identifier: " << originalIdentifier - << "- date:" << originalDate.toString(); - } - } - - completedTransactions = true; - - SettingsHolder* settingsHolder = SettingsHolder::instance(); - if (settingsHolder->hasSubscriptionTransaction(identifier)) { - logger.warning() << "This transaction has already been processed. Let's ignore it."; - } else { - completedTransactionIds.append(identifier); - } - - break; - } - case SKPaymentTransactionStatePurchasing: - logger.debug() << "transaction purchasing"; - break; - case SKPaymentTransactionStateDeferred: - logger.debug() << "transaction deferred"; - break; - default: - logger.warning() << "transaction unknown state"; - break; - } - } - - if (!completedTransactions && !canceledTransactions && !failedTransactions) { - // Nothing completed, nothing restored, nothing failed. Just purchasing transactions. - return; - } - - if (canceledTransactions) { - logger.debug() << "Subscription canceled"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); - } else if (failedTransactions) { - logger.error() << "Subscription failed"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); - } else if (completedTransactionIds.isEmpty()) { - Q_ASSERT(completedTransactions); - logger.debug() << "Subscription completed - but all the transactions are known"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); - } else if (AmneziaVPN::instance()->userAuthenticated()) { - Q_ASSERT(completedTransactions); - logger.debug() << "Subscription completed. Let's start the validation"; - QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection, - Q_ARG(QStringList, completedTransactionIds)); - } else { - Q_ASSERT(completedTransactions); - logger.debug() << "Subscription completed - but the user is not authenticated yet"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); - } - - for (SKPaymentTransaction* transaction in transactions) { - switch (transaction.transactionState) { - case SKPaymentTransactionStateFailed: - [[fallthrough]]; - case SKPaymentTransactionStateRestored: - [[fallthrough]]; - case SKPaymentTransactionStatePurchased: - [queue finishTransaction:transaction]; - break; - default: - break; - } - } -} - -- (void)requestDidFinish:(SKRequest*)request { - logger.debug() << "Receipt refreshed correctly"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection, - Q_ARG(QStringList, QStringList())); -} - -- (void)request:(SKRequest*)request didFailWithError:(NSError*)error { - logger.error() << "Failed to refresh the receipt" - << QString::fromNSString(error.localizedDescription); - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionFailed", Qt::QueuedConnection); -} - -@end - -IOSIAPHandler::IOSIAPHandler(QObject* parent) : IAPHandler(parent) { - MVPN_COUNT_CTOR(IOSIAPHandler); - - m_delegate = [[IOSIAPHandlerDelegate alloc] initWithObject:this]; - [[SKPaymentQueue defaultQueue] - addTransactionObserver:static_cast(m_delegate)]; -} - -IOSIAPHandler::~IOSIAPHandler() { - MVPN_COUNT_DTOR(IOSIAPHandler); - - IOSIAPHandlerDelegate* delegate = static_cast(m_delegate); - [[SKPaymentQueue defaultQueue] removeTransactionObserver:delegate]; - - [delegate dealloc]; - m_delegate = nullptr; -} - -void IOSIAPHandler::nativeRegisterProducts() { - NSSet* productIdentifiers = [NSSet set]; - for (const Product& product : m_products) { - productIdentifiers = [productIdentifiers setByAddingObject:product.m_name.toNSString()]; - } - - logger.debug() << "We are about to register" << [productIdentifiers count] << "products"; - - SKProductsRequest* productsRequest = - [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; - - IOSIAPHandlerDelegate* delegate = static_cast(m_delegate); - productsRequest.delegate = delegate; - [productsRequest start]; -} - -void IOSIAPHandler::nativeStartSubscription(Product* product) { - Q_ASSERT(product->m_extra); - SKProduct* skProduct = static_cast(product->m_extra); - SKPayment* payment = [SKPayment paymentWithProduct:skProduct]; - [[SKPaymentQueue defaultQueue] addPayment:payment]; -} - -void IOSIAPHandler::productRegistered(void* a_product) { - SKProduct* product = static_cast(a_product); - - Q_ASSERT(m_productsRegistrationState == eRegistering); - - logger.debug() << "Product registered"; - - NSString* nsProductIdentifier = [product productIdentifier]; - QString productIdentifier = QString::fromNSString(nsProductIdentifier); - - Product* productData = findProduct(productIdentifier); - Q_ASSERT(productData); - - logger.debug() << "Id:" << productIdentifier; - logger.debug() << "Title:" << QString::fromNSString([product localizedTitle]); - logger.debug() << "Description:" << QString::fromNSString([product localizedDescription]); - - QString priceValue; - { - NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; - [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; - [numberFormatter setNumberStyle:(NSNumberFormatterStyle)NSNumberFormatterCurrencyStyle]; - [numberFormatter setLocale:product.priceLocale]; - - NSString* price = [numberFormatter stringFromNumber:product.price]; - priceValue = QString::fromNSString(price); - [numberFormatter release]; - } - - logger.debug() << "Price:" << priceValue; - - QString monthlyPriceValue; - NSDecimalNumber* monthlyPriceNS = nullptr; - { - NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; - [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; - [numberFormatter setNumberStyle:(NSNumberFormatterStyle)NSNumberFormatterCurrencyStyle]; - [numberFormatter setLocale:product.priceLocale]; - - int32_t mounthCount = productTypeToMonthCount(productData->m_type); - Q_ASSERT(mounthCount >= 1); - - if (mounthCount == 1) { - monthlyPriceNS = product.price; - } else { - NSDecimalNumber* divider = [[NSDecimalNumber alloc] initWithDouble:(double)mounthCount]; - monthlyPriceNS = [product.price decimalNumberByDividingBy:divider]; - [divider release]; - } - - NSString* price = [numberFormatter stringFromNumber:monthlyPriceNS]; - monthlyPriceValue = QString::fromNSString(price); - - [numberFormatter release]; - } - - logger.debug() << "Monthly Price:" << monthlyPriceValue; - - productData->m_price = priceValue; - productData->m_monthlyPrice = monthlyPriceValue; - productData->m_nonLocalizedMonthlyPrice = [monthlyPriceNS doubleValue]; - productData->m_extra = product; -} - -void IOSIAPHandler::processCompletedTransactions(const QStringList& ids) { - logger.debug() << "process completed transactions"; - - if (m_subscriptionState != eActive) { - logger.warning() << "Random transaction to be completed. Let's ignore it"; - return; - } - - QString receipt = IOSUtils::IAPReceipt(); - if (receipt.isEmpty()) { - logger.warning() << "Empty receipt found"; - emit subscriptionFailed(); - return; - } - - NetworkRequest* request = NetworkRequest::createForIOSPurchase(this, receipt); - - connect(request, &NetworkRequest::requestFailed, - [this](QNetworkReply::NetworkError error, const QByteArray& data) { - logger.error() << "Purchase request failed" << error; - - if (m_subscriptionState != eActive) { - logger.warning() << "We have been canceled in the meantime"; - return; - } - - stopSubscription(); - - QJsonDocument json = QJsonDocument::fromJson(data); - if (!json.isObject()) { - AmneziaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit subscriptionFailed(); - return; - } - - QJsonObject obj = json.object(); - QJsonValue errorValue = obj.value("errno"); - if (!errorValue.isDouble()) { - AmneziaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit subscriptionFailed(); - return; - } - - int errorNumber = errorValue.toInt(); - if (errorNumber != 145) { - AmneziaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); - emit subscriptionFailed(); - return; - } - - emit alreadySubscribed(); - }); - - connect(request, &NetworkRequest::requestCompleted, [this, ids](const QByteArray&) { - logger.debug() << "Purchase request completed"; - SettingsHolder::instance()->addSubscriptionTransactions(ids); - - if (m_subscriptionState != eActive) { - logger.warning() << "We have been canceled in the meantime"; - return; - } - - stopSubscription(); - emit subscriptionCompleted(); - }); -} diff --git a/client/platforms/ios/iosinterface.swift b/client/platforms/ios/iosinterface.swift deleted file mode 100644 index 75c25011..00000000 --- a/client/platforms/ios/iosinterface.swift +++ /dev/null @@ -1,230 +0,0 @@ -import Foundation -import Darwin - -typealias InetFamily = UInt8 -typealias Flags = Int32 - -func destinationAddress(_ data: ifaddrs) -> UnsafeMutablePointer! { return data.ifa_dstaddr } -func socketLength4(_ addr: sockaddr) -> UInt32 { return socklen_t(addr.sa_len) } - -/** - * This class represents a network interface in your system. For example, `en0` with a certain IP address. - * It is a wrapper around the `getifaddrs` system call. - * - * Typical use of this class is to first call `Interface.allInterfaces()` and then use the properties of the interface(s) that you need. - * - * - See: `/usr/include/ifaddrs.h` - */ -open class Interface : CustomStringConvertible, CustomDebugStringConvertible { - public var id = UUID() - /// The network interface family (IPv4 or IPv6, otherwise error). - public enum Family : Int { - case ipv4 - case ipv6 - case other - - public func toString() -> String { - switch (self) { - case .ipv4: return "IPv4" - case .ipv6: return "IPv6" - default: return "other" - } - } - } - - /** - * Returns all network interfaces in your system. If you have an interface name (e.g. `en0`) that has - * multiple IP addresses (e.g. one IPv4 address and a few IPv6 addresses), then they will be returned - * as separate instances of Interface. - * - Returns: An array containing all network interfaces in your system. - */ - public static func allInterfaces() -> [Interface] { - var interfaces : [Interface] = [] - - var ifaddrsPtr : UnsafeMutablePointer? = nil - if getifaddrs(&ifaddrsPtr) == 0 { - var ifaddrPtr = ifaddrsPtr - while ifaddrPtr != nil { - let addr = ifaddrPtr?.pointee.ifa_addr.pointee - if addr?.sa_family == InetFamily(AF_INET) || addr?.sa_family == InetFamily(AF_INET6) { - interfaces.append(Interface(data: (ifaddrPtr?.pointee)!)) - } - ifaddrPtr = ifaddrPtr?.pointee.ifa_next - } - freeifaddrs(ifaddrsPtr) - } - - return interfaces - } - - /** - * Returns a new Interface instance that does not represent a real network interface, but can be used for (unit) testing. - * - Returns: An instance of Interface that does *not* represent a real network interface. - */ - public static func createTestDummy(_ name:String, family:Family, address:String, multicastSupported:Bool, broadcastAddress:String?) -> Interface - { - return Interface(name: name, family: family, address: address, netmask: nil, running: true, up: true, loopback: false, multicastSupported: multicastSupported, broadcastAddress: broadcastAddress) - } - - /** - * Initialize a new Interface with the given properties. - */ - public init(name:String, family:Family, address:String?, netmask:String?, running:Bool, up:Bool, loopback:Bool, multicastSupported:Bool, broadcastAddress:String?) { - self.name = name - self.family = family - self.address = address - self.netmask = netmask - self.running = running - self.up = up - self.loopback = loopback - self.multicastSupported = multicastSupported - self.broadcastAddress = broadcastAddress - } - - convenience init(data:ifaddrs) { - let flags = Flags(data.ifa_flags) - let broadcastValid : Bool = ((flags & IFF_BROADCAST) == IFF_BROADCAST) - self.init(name: String(cString: data.ifa_name), - family: Interface.extractFamily(data), - address: Interface.extractAddress(data.ifa_addr), - netmask: Interface.extractAddress(data.ifa_netmask), - running: ((flags & IFF_RUNNING) == IFF_RUNNING), - up: ((flags & IFF_UP) == IFF_UP), - loopback: ((flags & IFF_LOOPBACK) == IFF_LOOPBACK), - multicastSupported: ((flags & IFF_MULTICAST) == IFF_MULTICAST), - broadcastAddress: ((broadcastValid && destinationAddress(data) != nil) ? Interface.extractAddress(destinationAddress(data)) : nil)) - } - - fileprivate static func extractFamily(_ data:ifaddrs) -> Family { - var family : Family = .other - let addr = data.ifa_addr.pointee - if addr.sa_family == InetFamily(AF_INET) { - family = .ipv4 - } - else if addr.sa_family == InetFamily(AF_INET6) { - family = .ipv6 - } - else { - family = .other - } - return family - } - - fileprivate static func extractAddress(_ address: UnsafeMutablePointer?) -> String? { - guard let address = address else { return nil } - return address.withMemoryRebound(to: sockaddr_storage.self, capacity: 1) { - if (address.pointee.sa_family == sa_family_t(AF_INET)) { - return extractAddress_ipv4($0) - } - else if (address.pointee.sa_family == sa_family_t(AF_INET6)) { - return extractAddress_ipv6($0) - } - else { - return nil - } - } - } - - fileprivate static func extractAddress_ipv4(_ address:UnsafeMutablePointer) -> String? { - return address.withMemoryRebound(to: sockaddr.self, capacity: 1) { addr in - var address : String? = nil - var hostname = [CChar](repeating: 0, count: Int(2049)) - if (getnameinfo(&addr.pointee, socklen_t(socketLength4(addr.pointee)), &hostname, - socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) == 0) { - address = String(cString: hostname) - } - else { - // var error = String.fromCString(gai_strerror(errno))! - // println("ERROR: \(error)") - } - return address - - } - } - - fileprivate static func extractAddress_ipv6(_ address:UnsafeMutablePointer) -> String? { - var addr = address.pointee - var ip : [Int8] = [Int8](repeating: Int8(0), count: Int(INET6_ADDRSTRLEN)) - return inetNtoP(&addr, ip: &ip) - } - - fileprivate static func inetNtoP(_ addr:UnsafeMutablePointer, ip:UnsafeMutablePointer) -> String? { - return addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { addr6 in - let conversion:UnsafePointer = inet_ntop(AF_INET6, &addr6.pointee.sin6_addr, ip, socklen_t(INET6_ADDRSTRLEN)) - return String(cString: conversion) - } - } - - /** - * Creates the network format representation of the interface's IP address. Wraps `inet_pton`. - */ - open var addressBytes: [UInt8]? { - guard let addr = address else { return nil } - - let af:Int32 - let len:Int - switch family { - case .ipv4: - af = AF_INET - len = 4 - case .ipv6: - af = AF_INET6 - len = 16 - default: - return nil - } - var bytes = [UInt8](repeating: 0, count: len) - let result = inet_pton(af, addr, &bytes) - return ( result == 1 ) ? bytes : nil - } - - /// `IFF_RUNNING` flag of `ifaddrs->ifa_flags`. - open var isRunning: Bool { return running } - - /// `IFF_UP` flag of `ifaddrs->ifa_flags`. - open var isUp: Bool { return up } - - /// `IFF_LOOPBACK` flag of `ifaddrs->ifa_flags`. - open var isLoopback: Bool { return loopback } - - /// `IFF_MULTICAST` flag of `ifaddrs->ifa_flags`. - open var supportsMulticast: Bool { return multicastSupported } - - /// Field `ifaddrs->ifa_name`. - public let name : String - - /// Field `ifaddrs->ifa_addr->sa_family`. - public let family : Family - - /// Extracted from `ifaddrs->ifa_addr`, supports both IPv4 and IPv6. - public let address : String? - - /// Extracted from `ifaddrs->ifa_netmask`, supports both IPv4 and IPv6. - public let netmask : String? - - /// Extracted from `ifaddrs->ifa_dstaddr`. Not applicable for IPv6. - public let broadcastAddress : String? - - fileprivate let running : Bool - fileprivate let up : Bool - fileprivate let loopback : Bool - fileprivate let multicastSupported : Bool - - /// Returns the interface name. - open var description: String { return name } - - /// Returns a string containing a few properties of the Interface. - open var debugDescription: String { - var s = "Interface name:\(name) family:\(family)" - if let ip = address { - s += " ip:\(ip)" - } - s += isUp ? " (up)" : " (down)" - s += isRunning ? " (running)" : "(not running)" - return s - } -} - -#if swift(>=5) -extension Interface: Identifiable {} -#endif diff --git a/client/platforms/ios/iosnetworkwatcher.h b/client/platforms/ios/iosnetworkwatcher.h new file mode 100644 index 00000000..5b779a87 --- /dev/null +++ b/client/platforms/ios/iosnetworkwatcher.h @@ -0,0 +1,32 @@ +/* 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 IOSNETWORKWATCHER_H +#define IOSNETWORKWATCHER_H + +#include + +#include "networkwatcherimpl.h" + +class IOSNetworkWatcher : public NetworkWatcherImpl { + public: + explicit IOSNetworkWatcher(QObject* parent); + ~IOSNetworkWatcher(); + + void initialize() override; + NetworkWatcherImpl::TransportType getTransportType() override; + + private: + NetworkWatcherImpl::TransportType toTransportType(nw_path_t path); + void controllerStateChanged(); + + NetworkWatcherImpl::TransportType m_currentDefaultTransport = + NetworkWatcherImpl::TransportType_Unknown; + NetworkWatcherImpl::TransportType m_currentVPNTransport = + NetworkWatcherImpl::TransportType_Unknown; + nw_path_monitor_t m_networkMonitor = nil; + nw_connection_t m_observableConnection = nil; +}; + +#endif // IOSNETWORKWATCHER_H diff --git a/client/platforms/ios/iosnetworkwatcher.mm b/client/platforms/ios/iosnetworkwatcher.mm new file mode 100644 index 00000000..9c6ca411 --- /dev/null +++ b/client/platforms/ios/iosnetworkwatcher.mm @@ -0,0 +1,79 @@ +/* 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 "iosnetworkwatcher.h" + +#include "leakdetector.h" +#include "logger.h" + +#import + +namespace { +Logger logger("IOSNetworkWatcher"); +dispatch_queue_t s_queue = dispatch_queue_create("VPNNetwork.queue", DISPATCH_QUEUE_SERIAL); +} + +IOSNetworkWatcher::IOSNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) { + MZ_COUNT_CTOR(IOSNetworkWatcher); +} + +IOSNetworkWatcher::~IOSNetworkWatcher() { + MZ_COUNT_DTOR(IOSNetworkWatcher); + if (m_networkMonitor != nil) { + nw_path_monitor_cancel(m_networkMonitor); + nw_release(m_networkMonitor); + } +} + +void IOSNetworkWatcher::initialize() { + m_networkMonitor = nw_path_monitor_create(); + nw_path_monitor_set_queue(m_networkMonitor, s_queue); + nw_path_monitor_set_update_handler(m_networkMonitor, ^(nw_path_t _Nonnull path) { + m_currentDefaultTransport = toTransportType(path); + }); + nw_path_monitor_start(m_networkMonitor); + + //TODO IMPL FOR AMNEZIA +} + +NetworkWatcherImpl::TransportType IOSNetworkWatcher::getTransportType() { + //TODO IMPL FOR AMNEZIA + + if (m_observableConnection != nil) { + return m_currentVPNTransport; + } + // If we don't have an open tunnel-observer, m_currentVPNTransport is probably wrong. + return NetworkWatcherImpl::TransportType_Unknown; +} + +NetworkWatcherImpl::TransportType IOSNetworkWatcher::toTransportType(nw_path_t path) { + if (path == nil) { + return NetworkWatcherImpl::TransportType_Unknown; + } + auto status = nw_path_get_status(path); + if (status != nw_path_status_satisfied && status != nw_path_status_satisfiable) { + // We're offline. + return NetworkWatcherImpl::TransportType_None; + } + if (nw_path_uses_interface_type(path, nw_interface_type_wifi)) { + return NetworkWatcherImpl::TransportType_WiFi; + } + if (nw_path_uses_interface_type(path, nw_interface_type_wired)) { + return NetworkWatcherImpl::TransportType_Ethernet; + } + if (nw_path_uses_interface_type(path, nw_interface_type_cellular)) { + return NetworkWatcherImpl::TransportType_Cellular; + } + if (nw_path_uses_interface_type(path, nw_interface_type_other)) { + return NetworkWatcherImpl::TransportType_Other; + } + if (nw_path_uses_interface_type(path, nw_interface_type_loopback)) { + return NetworkWatcherImpl::TransportType_Other; + } + return NetworkWatcherImpl::TransportType_Unknown; +} + +void IOSNetworkWatcher::controllerStateChanged() { + //TODO IMPL FOR AMNEZIA +} diff --git a/client/platforms/ios/iosnotificationhandler.mm b/client/platforms/ios/iosnotificationhandler.mm index a83cd70b..efa48385 100644 --- a/client/platforms/ios/iosnotificationhandler.mm +++ b/client/platforms/ios/iosnotificationhandler.mm @@ -73,7 +73,7 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO]; - UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"mozillavpn" + UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn" content:content trigger:trigger]; diff --git a/client/platforms/ios/iosopenvpn2ssadapter.h b/client/platforms/ios/iosopenvpn2ssadapter.h deleted file mode 100644 index 2189189b..00000000 --- a/client/platforms/ios/iosopenvpn2ssadapter.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef iosopenvpn2ssadapter_h -#define iosopenvpn2ssadapter_h - -#import -#import "ssadapterpacketflow.h" -#import - -@interface ShadowSocksAdapterFlowBridge: NSObject - -@property (nonatomic, weak) id ssPacketFlow; -@property (nonatomic, readonly) CFSocketRef ssSocket; -@property (nonatomic, readonly) CFSocketRef packetFlowSocket; - -- (BOOL)configureSocketWithError:(NSError **)error; -- (void)invalidateSocketsIfNeeded; -- (void)processPackets; - -@end - - -#endif /* iosopenvpn2ssadapter_h */ diff --git a/client/platforms/ios/iosopenvpn2ssadapter.m b/client/platforms/ios/iosopenvpn2ssadapter.m deleted file mode 100644 index dcf4e249..00000000 --- a/client/platforms/ios/iosopenvpn2ssadapter.m +++ /dev/null @@ -1,139 +0,0 @@ -#import "iosopenvpn2ssadapter.h" - -#include -#include - -#import "sspacket.h" -#import "ssadapterpacketflow.h" - -@implementation ShadowSocksAdapterFlowBridge - -# pragma mark - Sockets Configuration - -static void SocketCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *obj) { - if (type != kCFSocketDataCallBack) { - return; - } - SSPacket *packet = [[SSPacket alloc] initWithSSData:data]; - ShadowSocksAdapterFlowBridge *bridge = (__bridge ShadowSocksAdapterFlowBridge*)obj; - [bridge writePackets:@[packet] toPacketFlow:bridge.ssPacketFlow]; -} - -- (BOOL)configureSocketWithError:(NSError * __autoreleasing *)error { - int sockets[2]; - if (socketpair(PF_LOCAL, SOCK_DGRAM, IPPROTO_IP, sockets) == -1) { - if (error) { - NSDictionary *userInfo = @{ - // TODO: handle - }; - *error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo]; - } - return NO; - } - CFSocketContext socketCtx = {0, (__bridge void *)self, NULL, NULL, NULL}; - _packetFlowSocket = CFSocketCreateWithNative(kCFAllocatorDefault, sockets[0], kCFSocketDataCallBack, SocketCallback, &socketCtx); - _ssSocket = CFSocketCreateWithNative(kCFAllocatorDefault, sockets[1], kCFSocketNoCallBack, NULL, NULL); - if (!(_packetFlowSocket && _ssSocket)) { - if (error) { - NSDictionary *userInfo = @{ - // TODO: handle - }; - *error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo]; - } - return NO; - } - if (!([self configureOptionsForSocket:_packetFlowSocket error:error] && [self configureOptionsForSocket:_ssSocket error:error])) { - return NO; - } - CFRunLoopSourceRef packetFlowSocketSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _packetFlowSocket, 0); - CFRunLoopAddSource(CFRunLoopGetMain(), packetFlowSocketSource, kCFRunLoopDefaultMode); - CFRelease(packetFlowSocketSource); - return YES; -} - -- (void)invalidateSocketsIfNeeded { - if (_ssSocket) { - CFSocketInvalidate(_ssSocket); - CFRelease(_ssSocket); - _ssSocket = NULL; - } - if (_packetFlowSocket) { - CFSocketInvalidate(_packetFlowSocket); - CFRelease(_packetFlowSocket); - _packetFlowSocket = NULL; - } -} - -- (void)processPackets { - NSAssert(self.ssPacketFlow != nil, @"packetFlow property shouldn't be nil, set it before start reading packets."); - __weak typeof(self) weakSelf = self; - [self.ssPacketFlow readPacketsWithCompletionHandler:^(NSArray *packets, NSArray *protocols) { - __strong typeof(self) self = weakSelf; - [self writePackets:packets protocols:protocols toSocket:self.packetFlowSocket]; - [self processPackets]; - }]; -} - -- (void)dealloc { - [self invalidateSocketsIfNeeded]; - [super dealloc]; -} - -# pragma mark - Socket configuration -- (BOOL)configureOptionsForSocket:(CFSocketRef)socket error:(NSError * __autoreleasing *)error { - CFSocketNativeHandle socketHandle = CFSocketGetNative(socket); - - int buf_value = 65536; - socklen_t buf_len = sizeof(buf_value); - - if (setsockopt(socketHandle, SOL_SOCKET, SO_RCVBUF, &buf_value, buf_len) == -1) { - if (error) { - NSDictionary *userInfo = @{ - // TODO: handle - }; - *error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo]; - } - - return NO; - } - - if (setsockopt(socketHandle, SOL_SOCKET, SO_SNDBUF, &buf_value, buf_len) == -1) { - if (error) { - NSDictionary *userInfo = @{ - // TODO: handle - }; - *error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo]; - } - - return NO; - } - return YES; -} - -# pragma mark - Protocol methods -- (void)writePackets:(NSArray *)packets toPacketFlow:(id)packetFlow { - NSAssert(self.ssPacketFlow != nil, @"packetFlow shouldn't be nil, check provided parameter before start writing packets."); - - NSMutableArray *flowPackets = [[NSMutableArray alloc] init]; - NSMutableArray *protocols = [[NSMutableArray alloc] init]; - - [packets enumerateObjectsUsingBlock:^(SSPacket * _Nonnull packet, NSUInteger idx, BOOL * _Nonnull stop) { - [flowPackets addObject:packet.ssPacketFlowData]; - [protocols addObject:packet.protocolFamily]; - }]; - - [packetFlow writePackets:flowPackets withProtocols:protocols]; -} - -- (void)writePackets:(NSArray *)packets protocols:(NSArray *)protocols toSocket:(CFSocketRef)socket { - if (socket == NULL) { return; } - - [packets enumerateObjectsUsingBlock:^(NSData *data, NSUInteger idx, BOOL *stop) { - NSNumber *protocolFamily = protocols[idx]; - SSPacket *packet = [[SSPacket alloc] initWithPacketFlowData:data protocolFamily:protocolFamily]; - - CFSocketSendData(socket, NULL, (CFDataRef)packet.vpnData, 0.05); - }]; -} - -@end diff --git a/client/platforms/ios/iosutils.h b/client/platforms/ios/iosutils.h deleted file mode 100644 index 6a73012b..00000000 --- a/client/platforms/ios/iosutils.h +++ /dev/null @@ -1,17 +0,0 @@ -/* 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 IOSUTILS_H -#define IOSUTILS_H - -#include - -class IOSUtils final { - public: - static QString computerName(); - - static QString IAPReceipt(); -}; - -#endif // IOSUTILS_H diff --git a/client/platforms/ios/iosutils.mm b/client/platforms/ios/iosutils.mm deleted file mode 100644 index 0e50fb6b..00000000 --- a/client/platforms/ios/iosutils.mm +++ /dev/null @@ -1,63 +0,0 @@ -/* 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 "iosutils.h" -#include "logger.h" - -#include -#include - -#import - -namespace { -Logger logger(LOG_IOS, "IOSUtils"); -} - -// static -QString IOSUtils::computerName() { - NSString* name = [[UIDevice currentDevice] name]; - return QString::fromNSString(name); -} - -// static -QString IOSUtils::IAPReceipt() { - logger.debug() << "Retrieving IAP receipt"; - - NSURL* receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; - NSData* receipt = [NSData dataWithContentsOfURL:receiptURL]; - - // All the following is for debug only. - NSString* path = [receiptURL path]; - Q_ASSERT(path); - - logger.debug() << "Receipt URL:" << QString::fromNSString(path); - - NSFileManager* fileManager = [NSFileManager defaultManager]; - Q_ASSERT(fileManager); - - NSDictionary* fileAttributes = [fileManager attributesOfItemAtPath:path error:NULL]; - if (fileAttributes) { - NSNumber* fileSize = [fileAttributes objectForKey:NSFileSize]; - if (fileSize) { - logger.debug() << "File size:" << [fileSize unsignedLongLongValue]; - } - - NSString* fileOwner = [fileAttributes objectForKey:NSFileOwnerAccountName]; - if (fileOwner) { - logger.debug() << "Owner:" << QString::fromNSString(fileOwner); - } - - NSDate* fileModDate = [fileAttributes objectForKey:NSFileModificationDate]; - if (fileModDate) { - logger.debug() << "Modification date:" << QDateTime::fromNSDate(fileModDate).toString(); - } - } - - if (!receipt) { - return QString(); - } - - NSString* encodedReceipt = [receipt base64EncodedStringWithOptions:0]; - return QString::fromNSString(encodedReceipt); -} diff --git a/client/platforms/ios/ipaddress.cpp b/client/platforms/ios/ipaddress.cpp deleted file mode 100644 index 501701c1..00000000 --- a/client/platforms/ios/ipaddress.cpp +++ /dev/null @@ -1,313 +0,0 @@ -/* 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 "ipaddress.h" -#include "bigintipv6addr.h" - -#include - -namespace { - -quint32 s_allIpV4Ones = static_cast(qPow(2, 32) - 1); - -BigIntIPv6Addr s_allIPv6Ones; -bool s_ipv6Initialized = false; - -void maybeInitialize() { - if (s_ipv6Initialized) return; - - s_ipv6Initialized = true; - - Q_IPV6ADDR allOnes; - memset((void*)&allOnes, static_cast(qPow(2, 8) - 1), sizeof(allOnes)); - - s_allIPv6Ones = BigIntIPv6Addr(allOnes); -} - -} // namespace - -// static -IPAddress IPAddress::create(const QString& ip) { - if (ip.contains("/")) { - QPair p = QHostAddress::parseSubnet(ip); - - if (p.first.protocol() == QAbstractSocket::IPv4Protocol) { - if (p.second < 32) { - return IPAddress(p.first, p.second); - } - return IPAddress(p.first); - } - - if (p.first.protocol() == QAbstractSocket::IPv6Protocol) { - if (p.second < 128) { - return IPAddress(p.first, p.second); - } - return IPAddress(p.first); - } - - Q_ASSERT(false); - } - - return IPAddress(QHostAddress(ip)); -} - -IPAddress::IPAddress() { - maybeInitialize(); -} - -IPAddress::IPAddress(const IPAddress& other) { - maybeInitialize(); - *this = other; -} - -IPAddress& IPAddress::operator=(const IPAddress& other) { - if (this == &other) return *this; - - m_address = other.m_address; - m_prefixLength = other.m_prefixLength; - m_netmask = other.m_netmask; - m_hostmask = other.m_hostmask; - m_broadcastAddress = other.m_broadcastAddress; - - return *this; -} - -IPAddress::IPAddress(const QHostAddress& address) - : m_address(address), m_broadcastAddress(address) { - maybeInitialize(); - - if (address.protocol() == QAbstractSocket::IPv4Protocol) { - m_prefixLength = 32; - m_netmask = QHostAddress(s_allIpV4Ones); - m_hostmask = QHostAddress((quint32)(0)); - } else { - Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol); - m_prefixLength = 128; - - m_netmask = QHostAddress(s_allIPv6Ones.value()); - - { - Q_IPV6ADDR ipv6; - memset((void*)&ipv6, 0, sizeof(ipv6)); - m_hostmask = QHostAddress(ipv6); - } - } -} - -IPAddress::IPAddress(const QHostAddress& address, int prefixLength) - : m_address(address), m_prefixLength(prefixLength) { - maybeInitialize(); - - if (address.protocol() == QAbstractSocket::IPv4Protocol) { - Q_ASSERT(prefixLength >= 0 && prefixLength <= 32); - m_netmask = QHostAddress(s_allIpV4Ones ^ (s_allIpV4Ones >> prefixLength)); - m_hostmask = QHostAddress(m_netmask.toIPv4Address() ^ s_allIpV4Ones); - m_broadcastAddress = - QHostAddress(address.toIPv4Address() | m_hostmask.toIPv4Address()); - } else { - Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol); - Q_ASSERT(prefixLength >= 0 && prefixLength <= 128); - - Q_IPV6ADDR netmask; - { - BigIntIPv6Addr tmp = (s_allIPv6Ones >> prefixLength); - for (int i = 0; i < 16; ++i) - netmask[i] = s_allIPv6Ones.value()[i] ^ tmp.value()[i]; - } - m_netmask = QHostAddress(netmask); - - { - Q_IPV6ADDR tmp; - for (int i = 0; i < 16; ++i) - tmp[i] = netmask[i] ^ s_allIPv6Ones.value()[i]; - m_hostmask = QHostAddress(tmp); - } - - { - Q_IPV6ADDR ipv6Address = address.toIPv6Address(); - Q_IPV6ADDR ipv6Hostname = m_hostmask.toIPv6Address(); - for (int i = 0; i < 16; ++i) ipv6Address[i] |= ipv6Hostname[i]; - m_broadcastAddress = QHostAddress(ipv6Address); - } - } -} - -IPAddress::~IPAddress() { } - -QAbstractSocket::NetworkLayerProtocol IPAddress::type() const { - return m_address.protocol(); -} - -bool IPAddress::overlaps(const IPAddress& other) const { - return other.contains(m_address) || other.contains(m_broadcastAddress) || - contains(other.m_address) || contains(other.m_broadcastAddress); -} - -bool IPAddress::contains(const QHostAddress& address) const { - if (address.protocol() != m_address.protocol()) { - return false; - } - - if (address.protocol() == QAbstractSocket::IPv4Protocol) { - return (m_address.toIPv4Address() <= address.toIPv4Address()) && - (address.toIPv4Address() <= m_broadcastAddress.toIPv4Address()); - } - - Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol); - return (BigIntIPv6Addr(m_address.toIPv6Address()) <= - BigIntIPv6Addr(address.toIPv6Address())) && - (BigIntIPv6Addr(address.toIPv6Address()) <= - BigIntIPv6Addr(m_broadcastAddress.toIPv6Address())); -} - -bool IPAddress::operator==(const IPAddress& other) const { - return m_address == other.m_address && m_netmask == other.m_netmask; -} - -bool IPAddress::subnetOf(const IPAddress& other) const { - if (other.m_address.protocol() != m_address.protocol()) { - return false; - } - - if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { - return other.m_address.toIPv4Address() <= m_address.toIPv4Address() && - other.m_broadcastAddress.toIPv4Address() >= - m_broadcastAddress.toIPv4Address(); - } - - Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol); - return BigIntIPv6Addr(other.m_address.toIPv6Address()) <= - BigIntIPv6Addr(m_address.toIPv6Address()) && - BigIntIPv6Addr(other.m_broadcastAddress.toIPv6Address()) >= - BigIntIPv6Addr(m_broadcastAddress.toIPv6Address()); -} - -QList IPAddress::subnets() const { - QList list; - - if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { - if (m_prefixLength == 32) { - list.append(*this); - return list; - } - - quint64 start = m_address.toIPv4Address(); - quint64 end = quint64(m_broadcastAddress.toIPv4Address()) + 1; - quint64 step = ((quint64)m_hostmask.toIPv4Address() + 1) >> 1; - - while (start < end) { - int newPrefixLength = m_prefixLength + 1; - if (newPrefixLength == 32) { - list.append(IPAddress(QHostAddress(static_cast(start)))); - } else { - list.append(IPAddress(QHostAddress(static_cast(start)), - m_prefixLength + 1)); - } - start += step; - } - - return list; - } - - Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol); - - if (m_prefixLength == 128) { - list.append(*this); - return list; - } - - BigInt start(17); - { - Q_IPV6ADDR addr = m_address.toIPv6Address(); - for (int i = 0; i < 16; ++i) { - start.setValueAt(addr[i], i + 1); - } - } - - BigInt end(17); - { - Q_IPV6ADDR addr = m_broadcastAddress.toIPv6Address(); - for (int i = 0; i < 16; ++i) { - end.setValueAt(addr[i], i + 1); - } - ++end; - } - - BigInt step(17); - { - Q_IPV6ADDR addr = m_hostmask.toIPv6Address(); - for (int i = 0; i < 16; ++i) { - step.setValueAt(addr[i], i + 1); - } - step = (++step) >> 1; - } - - while (start < end) { - int newPrefixLength = m_prefixLength + 1; - Q_IPV6ADDR startIPv6; - for (int i = 0; i < 16; ++i) { - startIPv6[i] = start.valueAt(i + 1); - } - - if (newPrefixLength == 128) { - list.append(IPAddress(QHostAddress(startIPv6))); - } else { - list.append(IPAddress(QHostAddress(startIPv6), m_prefixLength + 1)); - } - start += step; - } - - return list; -} - -// static -QList IPAddress::excludeAddresses( - const QList& sourceList, const QList& excludeList) { - QList results = sourceList; - - for (const IPAddress& exclude : excludeList) { - QList newResults; - - for (const IPAddress& ip : results) { - if (ip.overlaps(exclude)) { - QList range = ip.excludeAddresses(exclude); - newResults.append(range); - } else { - newResults.append(ip); - } - } - - results = newResults; - } - - return results; -} - -QList IPAddress::excludeAddresses(const IPAddress& ip) const { - QList sn = subnets(); - Q_ASSERT(sn.length() >= 2); - - QList result; - while (sn[0] != ip && sn[1] != ip) { - if (ip.subnetOf(sn[0])) { - result.append(sn[1]); - sn = sn[0].subnets(); - } else if (ip.subnetOf(sn[1])) { - result.append(sn[0]); - sn = sn[1].subnets(); - } else { - Q_ASSERT(false); - } - } - - if (sn[0] == ip) { - result.append(sn[1]); - } else if (sn[1] == ip) { - result.append(sn[0]); - } else { - Q_ASSERT(false); - } - - return result; -} diff --git a/client/platforms/macos/daemon/dnsutilsmacos.cpp b/client/platforms/macos/daemon/dnsutilsmacos.cpp index b4e61620..62a7a3a3 100644 --- a/client/platforms/macos/daemon/dnsutilsmacos.cpp +++ b/client/platforms/macos/daemon/dnsutilsmacos.cpp @@ -3,23 +3,24 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dnsutilsmacos.h" -#include "leakdetector.h" -#include "logger.h" + +#include +#include #include -#include -#include +#include "leakdetector.h" +#include "logger.h" namespace { -Logger logger(LOG_MACOS, "DnsUtilsMacos"); +Logger logger("DnsUtilsMacos"); } DnsUtilsMacos::DnsUtilsMacos(QObject* parent) : DnsUtils(parent) { - MVPN_COUNT_CTOR(DnsUtilsMacos); + MZ_COUNT_CTOR(DnsUtilsMacos); m_scStore = SCDynamicStoreCreate(kCFAllocatorSystemDefault, - CFSTR("mozillavpn"), nullptr, nullptr); + CFSTR("amneziavpn"), nullptr, nullptr); if (m_scStore == nullptr) { logger.error() << "Failed to create system configuration store ref"; } @@ -28,7 +29,7 @@ DnsUtilsMacos::DnsUtilsMacos(QObject* parent) : DnsUtils(parent) { } DnsUtilsMacos::~DnsUtilsMacos() { - MVPN_COUNT_DTOR(DnsUtilsMacos); + MZ_COUNT_DTOR(DnsUtilsMacos); restoreResolvers(); logger.debug() << "DnsUtilsMacos destroyed."; } diff --git a/client/platforms/macos/daemon/dnsutilsmacos.h b/client/platforms/macos/daemon/dnsutilsmacos.h index a916b9f4..de150a7b 100644 --- a/client/platforms/macos/daemon/dnsutilsmacos.h +++ b/client/platforms/macos/daemon/dnsutilsmacos.h @@ -5,14 +5,14 @@ #ifndef DNSUTILSMACOS_H #define DNSUTILSMACOS_H -#include "dnsutils.h" +#include +#include #include #include #include -#include -#include +#include "daemon/dnsutils.h" class DnsUtilsMacos final : public DnsUtils { Q_OBJECT diff --git a/client/platforms/macos/daemon/iputilsmacos.cpp b/client/platforms/macos/daemon/iputilsmacos.cpp index e5356f3f..0af093a4 100644 --- a/client/platforms/macos/daemon/iputilsmacos.cpp +++ b/client/platforms/macos/daemon/iputilsmacos.cpp @@ -3,13 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "iputilsmacos.h" -#include "leakdetector.h" -#include "logger.h" -#include "macosdaemon.h" -#include "daemon/wireguardutils.h" - -#include -#include #include #include @@ -19,33 +12,33 @@ #include #include +#include +#include + +#include "daemon/wireguardutils.h" +#include "leakdetector.h" +#include "logger.h" +#include "macosdaemon.h" + constexpr uint32_t ETH_MTU = 1500; constexpr uint32_t WG_MTU_OVERHEAD = 80; namespace { -Logger logger(LOG_MACOS, "IPUtilsMacos"); +Logger logger("IPUtilsMacos"); } IPUtilsMacos::IPUtilsMacos(QObject* parent) : IPUtils(parent) { - MVPN_COUNT_CTOR(IPUtilsMacos); + MZ_COUNT_CTOR(IPUtilsMacos); logger.debug() << "IPUtilsMacos created."; } IPUtilsMacos::~IPUtilsMacos() { - MVPN_COUNT_DTOR(IPUtilsMacos); + MZ_COUNT_DTOR(IPUtilsMacos); logger.debug() << "IPUtilsMacos destroyed."; } bool IPUtilsMacos::addInterfaceIPs(const InterfaceConfig& config) { - if (!addIP4AddressToDevice(config)) { - return false; - } - if (config.m_ipv6Enabled) { - if (!addIP6AddressToDevice(config)) { - return false; - } - } - return true; + return addIP4AddressToDevice(config) && addIP6AddressToDevice(config); } bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) { @@ -132,7 +125,7 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) { // Set ifr to interface int ret = ioctl(sockfd, SIOCAIFADDR, &ifr); if (ret) { - logger.error() << "Failed to set IPv4: " << deviceAddr + logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr) << "error:" << strerror(errno); return false; } @@ -172,7 +165,7 @@ bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) { // Set ifr to interface int ret = ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6); if (ret) { - logger.error() << "Failed to set IPv6: " << deviceAddr + logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr) << "error:" << strerror(errno); return false; } diff --git a/client/platforms/macos/daemon/iputilsmacos.h b/client/platforms/macos/daemon/iputilsmacos.h index 048e6965..6eee9391 100644 --- a/client/platforms/macos/daemon/iputilsmacos.h +++ b/client/platforms/macos/daemon/iputilsmacos.h @@ -5,10 +5,10 @@ #ifndef IPUTILSMACOS_H #define IPUTILSMACOS_H -#include "daemon/iputils.h" - #include +#include "daemon/iputils.h" + class IPUtilsMacos final : public IPUtils { public: IPUtilsMacos(QObject* parent); diff --git a/client/platforms/macos/daemon/macosdaemon.cpp b/client/platforms/macos/daemon/macosdaemon.cpp index 64155e1b..705b5dbb 100644 --- a/client/platforms/macos/daemon/macosdaemon.cpp +++ b/client/platforms/macos/daemon/macosdaemon.cpp @@ -3,9 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosdaemon.h" -#include "leakdetector.h" -#include "logger.h" -#include "wgquickprocess.h" #include #include @@ -18,13 +15,16 @@ #include #include +#include "leakdetector.h" +#include "logger.h" + namespace { -Logger logger(LOG_MACOS, "MacOSDaemon"); +Logger logger("MacOSDaemon"); MacOSDaemon* s_daemon = nullptr; } // namespace MacOSDaemon::MacOSDaemon() : Daemon(nullptr) { - MVPN_COUNT_CTOR(MacOSDaemon); + MZ_COUNT_CTOR(MacOSDaemon); logger.debug() << "Daemon created"; @@ -37,7 +37,7 @@ MacOSDaemon::MacOSDaemon() : Daemon(nullptr) { } MacOSDaemon::~MacOSDaemon() { - MVPN_COUNT_DTOR(MacOSDaemon); + MZ_COUNT_DTOR(MacOSDaemon); logger.debug() << "Daemon released"; @@ -50,25 +50,3 @@ MacOSDaemon* MacOSDaemon::instance() { Q_ASSERT(s_daemon); return s_daemon; } - -QByteArray MacOSDaemon::getStatus() { - logger.debug() << "Status request"; - - bool connected = m_connections.contains(0); - QJsonObject obj; - obj.insert("type", "status"); - obj.insert("connected", connected); - - if (connected) { - const ConnectionState& state = m_connections.value(0).m_config; - WireguardUtils::peerStatus status = - m_wgutils->getPeerStatus(state.m_config.m_serverPublicKey); - obj.insert("serverIpv4Gateway", state.m_config.m_serverIpv4Gateway); - obj.insert("deviceIpv4Address", state.m_config.m_deviceIpv4Address); - obj.insert("date", state.m_date.toString()); - obj.insert("txBytes", QJsonValue(status.txBytes)); - obj.insert("rxBytes", QJsonValue(status.rxBytes)); - } - - return QJsonDocument(obj).toJson(QJsonDocument::Compact); -} diff --git a/client/platforms/macos/daemon/macosdaemon.h b/client/platforms/macos/daemon/macosdaemon.h index 74db2da4..a48c326c 100644 --- a/client/platforms/macos/daemon/macosdaemon.h +++ b/client/platforms/macos/daemon/macosdaemon.h @@ -5,7 +5,7 @@ #ifndef MACOSDAEMON_H #define MACOSDAEMON_H -#include "daemon.h" +#include "daemon/daemon.h" #include "dnsutilsmacos.h" #include "iputilsmacos.h" #include "wireguardutilsmacos.h" @@ -19,8 +19,6 @@ class MacOSDaemon final : public Daemon { static MacOSDaemon* instance(); - QByteArray getStatus() override; - protected: WireguardUtils* wgutils() const override { return m_wgutils; } bool supportDnsUtils() const override { return true; } diff --git a/client/platforms/macos/daemon/macosdaemonserver.cpp b/client/platforms/macos/daemon/macosdaemonserver.cpp deleted file mode 100644 index f0bd33d6..00000000 --- a/client/platforms/macos/daemon/macosdaemonserver.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* 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 "macosdaemonserver.h" -#include "commandlineparser.h" -#include "daemon/daemonlocalserver.h" -#include "leakdetector.h" -#include "logger.h" -#include "macosdaemon.h" -#include "mozillavpn.h" -#include "signalhandler.h" - -#include - -namespace { -Logger logger(LOG_MACOS, "MacOSDaemonServer"); -} - -MacOSDaemonServer::MacOSDaemonServer(QObject* parent) - : Command(parent, "macosdaemon", "Activate the macos daemon") { - MVPN_COUNT_CTOR(MacOSDaemonServer); -} - -MacOSDaemonServer::~MacOSDaemonServer() { MVPN_COUNT_DTOR(MacOSDaemonServer); } - -int MacOSDaemonServer::run(QStringList& tokens) { - Q_ASSERT(!tokens.isEmpty()); - QString appName = tokens[0]; - - QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv()); - - QCoreApplication::setApplicationName("Mozilla VPN Daemon"); - QCoreApplication::setApplicationVersion(APP_VERSION); - - if (tokens.length() > 1) { - QList options; - return CommandLineParser::unknownOption(this, appName, tokens[1], options, - false); - } - - MacOSDaemon daemon; - - DaemonLocalServer server(qApp); - if (!server.initialize()) { - logger.error() << "Failed to initialize the server"; - return 1; - } - - // Signal handling for a proper shutdown. - SignalHandler sh; - QObject::connect(&sh, &SignalHandler::quitRequested, - []() { MacOSDaemon::instance()->deactivate(); }); - QObject::connect(&sh, &SignalHandler::quitRequested, &app, - &QCoreApplication::quit, Qt::QueuedConnection); - - return app.exec(); -} - -static Command::RegistrationProxy s_commandMacOSDaemon; diff --git a/client/platforms/macos/daemon/macosdaemonserver.h b/client/platforms/macos/daemon/macosdaemonserver.h deleted file mode 100644 index 5447b275..00000000 --- a/client/platforms/macos/daemon/macosdaemonserver.h +++ /dev/null @@ -1,18 +0,0 @@ -/* 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 MACOSDAEMONSERVER_H -#define MACOSDAEMONSERVER_H - -#include "command.h" - -class MacOSDaemonServer final : public Command { - public: - explicit MacOSDaemonServer(QObject* parent); - ~MacOSDaemonServer(); - - int run(QStringList& tokens) override; -}; - -#endif // MACOSDAEMONSERVER_H diff --git a/client/platforms/macos/daemon/macosroutemonitor.cpp b/client/platforms/macos/daemon/macosroutemonitor.cpp index 9dcff773..25f4662b 100644 --- a/client/platforms/macos/daemon/macosroutemonitor.cpp +++ b/client/platforms/macos/daemon/macosroutemonitor.cpp @@ -3,15 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosroutemonitor.h" -#include "leakdetector.h" -#include "logger.h" - -#include - -#include -#include -#include -#include #include #include @@ -24,24 +15,21 @@ #include #include +#include +#include +#include +#include + +#include "leakdetector.h" +#include "logger.h" + namespace { -Logger logger(LOG_MACOS, "MacosRouteMonitor"); - -template -static T* sockaddr_cast(QByteArray& data) { - const struct sockaddr* sa = (const struct sockaddr*)data.constData(); - Q_ASSERT(sa->sa_len <= data.length()); - if (data.length() >= (int)sizeof(T)) { - return (T*)data.data(); - } - return nullptr; -} - +Logger logger("MacosRouteMonitor"); } // namespace MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent) : QObject(parent), m_ifname(ifname) { - MVPN_COUNT_CTOR(MacosRouteMonitor); + MZ_COUNT_CTOR(MacosRouteMonitor); logger.debug() << "MacosRouteMonitor created."; m_rtsock = socket(PF_ROUTE, SOCK_RAW, 0); @@ -50,96 +38,250 @@ MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent) return; } - // Disable replies to our own messages. - int off = 0; - setsockopt(m_rtsock, SOL_SOCKET, SO_USELOOPBACK, &off, sizeof(off)); + m_ifindex = if_nametoindex(qPrintable(ifname)); m_notifier = new QSocketNotifier(m_rtsock, QSocketNotifier::Read, this); connect(m_notifier, &QSocketNotifier::activated, this, &MacosRouteMonitor::rtsockReady); + + // Grab the default routes at startup. + rtmFetchRoutes(AF_INET); + rtmFetchRoutes(AF_INET6); } MacosRouteMonitor::~MacosRouteMonitor() { - MVPN_COUNT_DTOR(MacosRouteMonitor); + MZ_COUNT_DTOR(MacosRouteMonitor); + flushExclusionRoutes(); if (m_rtsock >= 0) { close(m_rtsock); } logger.debug() << "MacosRouteMonitor destroyed."; } -void MacosRouteMonitor::handleRtmAdd(const struct rt_msghdr* rtm, - const QByteArray& payload) { - QList addrlist = parseAddrList(payload); - - // Ignore routing changes on the tunnel interfaces - if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) { - if (m_ifname == addrToString(addrlist[1])) { - return; - } +// Compare memory against zero. +static int memcmpzero(const void* data, size_t len) { + const quint8* ptr = static_cast(data); + while (len--) { + if (*ptr++) return 1; } - - QStringList list; - for (auto addr : addrlist) { - list.append(addrToString(addr)); - } - logger.debug() << "Route added by" << rtm->rtm_pid - << QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" "); + return 0; } void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm, const QByteArray& payload) { QList addrlist = parseAddrList(payload); - // Ignore routing changes on the tunnel interfaces - if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) { - if (m_ifname == addrToString(addrlist[1])) { - return; - } + // Ignore routing changes on the tunnel interface. + if (rtm->rtm_index == m_ifindex) { + return; } QStringList list; +#ifdef MZ_DEBUG for (auto addr : addrlist) { list.append(addrToString(addr)); } - logger.debug() << "Route deleted by" << rtm->rtm_pid - << QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" "); +#endif + char ifname[IF_NAMESIZE] = "null"; + if (rtm->rtm_index != 0) { + if_indextoname(rtm->rtm_index, ifname); + } + logger.debug() << "Route deleted via" << ifname + << QString("addrs(%1):").arg(rtm->rtm_addrs, 0, 16) + << list.join(" "); + + // We expect all useful routes to contain a destination, netmask and gateway. + if (!(rtm->rtm_addrs & RTA_DST) || !(rtm->rtm_addrs & RTA_GATEWAY) || + !(rtm->rtm_addrs & RTA_NETMASK) || (addrlist.count() < 3)) { + return; + } + + // Check for a default route, which should have a netmask of zero. + const struct sockaddr* sa = + reinterpret_cast(addrlist[2].constData()); + if (sa->sa_family == AF_INET) { + struct sockaddr_in sin; + Q_ASSERT(sa->sa_len <= sizeof(sin)); + memset(&sin, 0, sizeof(sin)); + memcpy(&sin, sa, sa->sa_len); + if (memcmpzero(&sin.sin_addr, sizeof(sin.sin_addr)) != 0) { + return; + } + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 sin6; + Q_ASSERT(sa->sa_len <= sizeof(sin6)); + memset(&sin6, 0, sizeof(sin6)); + memcpy(&sin6, sa, sa->sa_len); + if (memcmpzero(&sin6.sin6_addr, sizeof(sin6.sin6_addr)) != 0) { + return; + } + } else if (sa->sa_family != AF_UNSPEC) { + // We have sometimes seen the default route reported with AF_UNSPEC. + return; + } + + // Clear the default gateway + const struct sockaddr* dst = + reinterpret_cast(addrlist[0].constData()); + QAbstractSocket::NetworkLayerProtocol protocol; + unsigned int plen; + if (dst->sa_family == AF_INET) { + m_defaultGatewayIpv4.clear(); + m_defaultIfindexIpv4 = 0; + protocol = QAbstractSocket::IPv4Protocol; + plen = 32; + } else if (dst->sa_family == AF_INET6) { + m_defaultGatewayIpv6.clear(); + m_defaultIfindexIpv6 = 0; + protocol = QAbstractSocket::IPv6Protocol; + plen = 128; + } + + logger.debug() << "Lost default route via" << ifname + << logger.sensitive(addrToString(addrlist[1])); + for (const QHostAddress& addr : m_exclusionRoutes) { + if (addr.protocol() == protocol) { + logger.debug() << "Removing exclusion route to" + << logger.sensitive(addr.toString()); + rtmSendRoute(RTM_DELETE, addr, plen, rtm->rtm_index, nullptr); + } + } } -void MacosRouteMonitor::handleRtmChange(const struct rt_msghdr* rtm, +void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm, const QByteArray& payload) { QList addrlist = parseAddrList(payload); + int ifindex = rtm->rtm_index; + char ifname[IF_NAMESIZE] = "null"; - // Ignore routing changes on the tunnel interfaces - if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) { - if (m_ifname == addrToString(addrlist[1])) { + // We expect all useful routes to contain a destination, netmask and gateway. + if (!(rtm->rtm_addrs & RTA_DST) || !(rtm->rtm_addrs & RTA_GATEWAY) || + !(rtm->rtm_addrs & RTA_NETMASK) || (addrlist.count() < 3)) { + return; + } + // Ignore route changes that we caused, or routes on the tunnel interface. + if (rtm->rtm_index == m_ifindex) { + return; + } + if ((rtm->rtm_pid == getpid()) && (rtm->rtm_type != RTM_GET)) { + return; + } + + // Special case: If RTA_IFP is set, then we should get the interface index + // from the address list instead of rtm_index. + if (rtm->rtm_addrs & RTA_IFP) { + int addridx = 0; + for (int mask = 1; mask < RTA_IFP; mask <<= 1) { + if (rtm->rtm_addrs & mask) { + addridx++; + } + } + if (addridx >= addrlist.count()) { return; } + const char* sdl_data = addrlist[addridx].constData(); + const struct sockaddr_dl* sdl = + reinterpret_cast(sdl_data); + if (sdl->sdl_family == AF_LINK) { + ifindex = sdl->sdl_index; + } } + // Log relevant updates to the routing table. QStringList list; +#ifdef MZ_DEBUG for (auto addr : addrlist) { list.append(addrToString(addr)); } - logger.debug() << "Route changed by" << rtm->rtm_pid - << QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" "); +#endif + if_indextoname(ifindex, ifname); + logger.debug() << "Route update via" << ifname + << QString("addrs(%1):").arg(rtm->rtm_addrs, 0, 16) + << list.join(" "); + + // Check for a default route, which should have a netmask of zero. + const struct sockaddr* sa = + reinterpret_cast(addrlist[2].constData()); + if (sa->sa_family == AF_INET) { + struct sockaddr_in sin; + Q_ASSERT(sa->sa_len <= sizeof(sin)); + memset(&sin, 0, sizeof(sin)); + memcpy(&sin, sa, sa->sa_len); + if (memcmpzero(&sin.sin_addr, sizeof(sin.sin_addr)) != 0) { + return; + } + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 sin6; + Q_ASSERT(sa->sa_len <= sizeof(sin6)); + memset(&sin6, 0, sizeof(sin6)); + memcpy(&sin6, sa, sa->sa_len); + if (memcmpzero(&sin6.sin6_addr, sizeof(sin6.sin6_addr)) != 0) { + return; + } + } else if (sa->sa_family != AF_UNSPEC) { + // The default route sometimes sets a netmask of AF_UNSPEC. + return; + } + + // Determine if this is the IPv4 or IPv6 default route. + const struct sockaddr* dst = + reinterpret_cast(addrlist[0].constData()); + QAbstractSocket::NetworkLayerProtocol protocol; + unsigned int plen; + int rtm_type = RTM_ADD; + if (dst->sa_family == AF_INET) { + if (m_defaultIfindexIpv4 != 0) { + rtm_type = RTM_CHANGE; + } + m_defaultGatewayIpv4 = addrlist[1]; + m_defaultIfindexIpv4 = ifindex; + protocol = QAbstractSocket::IPv4Protocol; + plen = 32; + } else if (dst->sa_family == AF_INET6) { + if (m_defaultIfindexIpv6 != 0) { + rtm_type = RTM_CHANGE; + } + m_defaultGatewayIpv6 = addrlist[1]; + m_defaultIfindexIpv6 = ifindex; + protocol = QAbstractSocket::IPv6Protocol; + plen = 128; + } else { + return; + } + + // Update the exclusion routes with the new default route. + logger.debug() << "Updating default route via" << ifname + << addrToString(addrlist[1]); + for (const QHostAddress& addr : m_exclusionRoutes) { + if (addr.protocol() == protocol) { + logger.debug() << "Updating exclusion route to" + << logger.sensitive(addr.toString()); + rtmSendRoute(rtm_type, addr, plen, ifindex, addrlist[1].constData()); + } + } } void MacosRouteMonitor::handleIfaceInfo(const struct if_msghdr* ifm, const QByteArray& payload) { - QList addrlist = parseAddrList(payload); + QStringList list; if (ifm->ifm_index != if_nametoindex(qPrintable(m_ifname))) { return; } m_ifflags = ifm->ifm_flags; - QStringList list; +#ifdef MZ_DEBUG + QList addrlist = parseAddrList(payload); for (auto addr : addrlist) { list.append(addrToString(addr)); } - logger.debug() << "Interface " << ifm->ifm_index - << "changed flags:" << ifm->ifm_flags - << QString("addrs(%1):").arg(ifm->ifm_addrs) << list.join(" "); +#else + Q_UNUSED(payload); +#endif + logger.debug() << "Interface" << ifm->ifm_index + << "chagned flags:" << ifm->ifm_flags + << QString("addrs(%1):").arg(ifm->ifm_addrs, 0, 16) + << list.join(" "); } void MacosRouteMonitor::rtsockReady() { @@ -154,11 +296,13 @@ void MacosRouteMonitor::rtsockReady() { (struct rt_msghdr*)((char*)(_rtm_) + (_rtm_)->rtm_msglen) #endif - struct rt_msghdr* rtm = (struct rt_msghdr*)buf; - struct rt_msghdr* end = (struct rt_msghdr*)(&buf[len]); + struct rt_msghdr* rtm = reinterpret_cast(buf); + struct rt_msghdr* end = reinterpret_cast(&buf[len]); while (rtm < end) { // Ensure the message fits within the buffer if (RTMSG_NEXT(rtm) > end) { + logger.debug() << "Routing message overflowed with length" + << rtm->rtm_msglen; break; } @@ -167,7 +311,7 @@ void MacosRouteMonitor::rtsockReady() { switch (rtm->rtm_type) { case RTM_ADD: message.remove(0, sizeof(struct rt_msghdr)); - handleRtmAdd(rtm, message); + handleRtmUpdate(rtm, message); break; case RTM_DELETE: message.remove(0, sizeof(struct rt_msghdr)); @@ -175,14 +319,17 @@ void MacosRouteMonitor::rtsockReady() { break; case RTM_CHANGE: message.remove(0, sizeof(struct rt_msghdr)); - handleRtmChange(rtm, message); + handleRtmUpdate(rtm, message); + break; + case RTM_GET: + message.remove(0, sizeof(struct rt_msghdr)); + handleRtmUpdate(rtm, message); break; case RTM_IFINFO: message.remove(0, sizeof(struct if_msghdr)); handleIfaceInfo((struct if_msghdr*)rtm, message); break; default: - logger.debug() << "Unknown routing message:" << rtm->rtm_type; break; } @@ -192,7 +339,7 @@ void MacosRouteMonitor::rtsockReady() { void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr, const void* sa) { - size_t sa_len = ((struct sockaddr*)sa)->sa_len; + size_t sa_len = static_cast(sa)->sa_len; Q_ASSERT((rtm->rtm_addrs & rtaddr) == 0); if ((rtm->rtm_msglen + sa_len) > maxlen) { return; @@ -206,18 +353,19 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, } } -bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) { +bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix, + unsigned int plen, unsigned int ifindex, + const void* gateway) { constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in6) * 2 + - sizeof(struct sockaddr_dl); - char buf[rtm_max_size]; - struct rt_msghdr* rtm = (struct rt_msghdr*)buf; + sizeof(struct sockaddr_storage); + char buf[rtm_max_size] = {0}; + struct rt_msghdr* rtm = reinterpret_cast(buf); - memset(rtm, 0, rtm_max_size); rtm->rtm_msglen = sizeof(struct rt_msghdr); rtm->rtm_version = RTM_VERSION; rtm->rtm_type = action; - rtm->rtm_index = if_nametoindex(qPrintable(m_ifname)); + rtm->rtm_index = ifindex; rtm->rtm_flags = RTF_STATIC | RTF_UP; rtm->rtm_addrs = 0; rtm->rtm_pid = 0; @@ -227,9 +375,9 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) { memset(&rtm->rtm_rmx, 0, sizeof(rtm->rtm_rmx)); // Append RTA_DST - if (prefix.type() == IPAddressRange::IPv6) { + if (prefix.protocol() == QAbstractSocket::IPv6Protocol) { struct sockaddr_in6 sin6; - Q_IPV6ADDR dst = QHostAddress(prefix.ipAddress()).toIPv6Address(); + Q_IPV6ADDR dst = prefix.toIPv6Address(); memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_len = sizeof(sin6); @@ -237,7 +385,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) { rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6); } else { struct sockaddr_in sin; - quint32 dst = QHostAddress(prefix.ipAddress()).toIPv4Address(); + quint32 dst = prefix.toIPv4Address(); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_len = sizeof(sin); @@ -246,23 +394,16 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) { } // Append RTA_GATEWAY - if (action != RTM_DELETE) { - struct sockaddr_dl sdl; - memset(&sdl, 0, sizeof(sdl)); - sdl.sdl_family = AF_LINK; - sdl.sdl_len = offsetof(struct sockaddr_dl, sdl_data) + m_ifname.length(); - sdl.sdl_index = rtm->rtm_index; - sdl.sdl_type = IFT_OTHER; - sdl.sdl_nlen = m_ifname.length(); - sdl.sdl_alen = 0; - sdl.sdl_slen = 0; - memcpy(&sdl.sdl_data, qPrintable(m_ifname), sdl.sdl_nlen); - rtmAppendAddr(rtm, rtm_max_size, RTA_GATEWAY, &sdl); + if (gateway != nullptr) { + int family = static_cast(gateway)->sa_family; + if ((family == AF_INET) || (family == AF_INET6)) { + rtm->rtm_flags |= RTF_GATEWAY; + } + rtmAppendAddr(rtm, rtm_max_size, RTA_GATEWAY, gateway); } // Append RTA_NETMASK - int plen = prefix.range(); - if (prefix.type() == IPAddressRange::IPv6) { + if (prefix.protocol() == QAbstractSocket::IPv6Protocol) { struct sockaddr_in6 sin6; memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; @@ -272,7 +413,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) { sin6.sin6_addr.s6_addr[plen / 8] = 0xFF ^ (0xFF >> (plen % 8)); } rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin6); - } else if (prefix.type() == IPAddressRange::IPv4) { + } else if (prefix.protocol() == QAbstractSocket::IPv4Protocol) { struct sockaddr_in sin; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; @@ -299,12 +440,122 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) { return false; } -bool MacosRouteMonitor::insertRoute(const IPAddressRange& prefix) { - return rtmSendRoute(RTM_ADD, prefix); +bool MacosRouteMonitor::rtmFetchRoutes(int family) { + constexpr size_t rtm_max_size = + sizeof(struct rt_msghdr) + sizeof(struct sockaddr_storage) * 2; + char buf[rtm_max_size] = {0}; + struct rt_msghdr* rtm = reinterpret_cast(buf); + + rtm->rtm_msglen = sizeof(struct rt_msghdr); + rtm->rtm_version = RTM_VERSION; + rtm->rtm_type = RTM_GET; + rtm->rtm_flags = RTF_UP | RTF_GATEWAY; + rtm->rtm_addrs = 0; + rtm->rtm_pid = 0; + rtm->rtm_seq = m_rtseq++; + rtm->rtm_errno = 0; + rtm->rtm_inits = 0; + memset(&rtm->rtm_rmx, 0, sizeof(rtm->rtm_rmx)); + + if (family == AF_INET) { + struct sockaddr_in sin; + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(struct sockaddr_in); + rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin); + rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin); + } else if (family == AF_INET6) { + struct sockaddr_in6 sin6; + memset(&sin6, 0, sizeof(struct sockaddr_in6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6); + rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin6); + } else { + logger.warning() << "Unsupported address family"; + return false; + } + + // Send the routing message into the kernel. + int len = write(m_rtsock, rtm, rtm->rtm_msglen); + if (len == rtm->rtm_msglen) { + return true; + } + logger.warning() << "Failed to request routing table:" << strerror(errno); + return false; } -bool MacosRouteMonitor::deleteRoute(const IPAddressRange& prefix) { - return rtmSendRoute(RTM_DELETE, prefix); +bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) { + struct sockaddr_dl datalink; + memset(&datalink, 0, sizeof(datalink)); + datalink.sdl_family = AF_LINK; + datalink.sdl_len = offsetof(struct sockaddr_dl, sdl_data) + m_ifname.length(); + datalink.sdl_index = m_ifindex; + datalink.sdl_type = IFT_OTHER; + datalink.sdl_nlen = m_ifname.length(); + datalink.sdl_alen = 0; + datalink.sdl_slen = 0; + memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen); + + return rtmSendRoute(RTM_ADD, prefix.address(), prefix.prefixLength(), + m_ifindex, &datalink); +} + +bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) { + return rtmSendRoute(RTM_DELETE, prefix.address(), prefix.prefixLength(), + m_ifindex, nullptr); +} + +bool MacosRouteMonitor::addExclusionRoute(const QHostAddress& address) { + logger.debug() << "Adding exclusion route for" + << logger.sensitive(address.toString()); + + if (m_exclusionRoutes.contains(address)) { + logger.warning() << "Exclusion route already exists"; + return false; + } + m_exclusionRoutes.append(address); + + // If the default route is known, then updte the routing table immediately. + if ((address.protocol() == QAbstractSocket::IPv4Protocol) && + (m_defaultIfindexIpv4 != 0) && !m_defaultGatewayIpv4.isEmpty()) { + return rtmSendRoute(RTM_ADD, address, 32, m_defaultIfindexIpv4, + m_defaultGatewayIpv4.constData()); + } + if ((address.protocol() == QAbstractSocket::IPv6Protocol) && + (m_defaultIfindexIpv6 != 0) && !m_defaultGatewayIpv6.isEmpty()) { + return rtmSendRoute(RTM_ADD, address, 128, m_defaultIfindexIpv6, + m_defaultGatewayIpv6.constData()); + } + + // Otherwise, the default route isn't known yet. Do nothing. + return true; +} + +bool MacosRouteMonitor::deleteExclusionRoute(const QHostAddress& address) { + logger.debug() << "Deleting exclusion route for" + << logger.sensitive(address.toString()); + + m_exclusionRoutes.removeAll(address); + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + return rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr); + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + return rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6, + nullptr); + } else { + return false; + } +} + +void MacosRouteMonitor::flushExclusionRoutes() { + while (!m_exclusionRoutes.isEmpty()) { + QHostAddress address = m_exclusionRoutes.takeFirst(); + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr); + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6, nullptr); + } + } } // static @@ -341,7 +592,10 @@ QString MacosRouteMonitor::addrToString(const struct sockaddr* sa) { } if (sa->sa_family == AF_LINK) { const struct sockaddr_dl* sdl = (const struct sockaddr_dl*)sa; - return QString(link_ntoa(sdl)); + return QString("link#%1:").arg(sdl->sdl_index) + QString(link_ntoa(sdl)); + } + if (sa->sa_family == AF_UNSPEC) { + return QString("unspec"); } return QString("unknown(af=%1)").arg(sa->sa_family); } diff --git a/client/platforms/macos/daemon/macosroutemonitor.h b/client/platforms/macos/daemon/macosroutemonitor.h index a912f972..2d5c54bb 100644 --- a/client/platforms/macos/daemon/macosroutemonitor.h +++ b/client/platforms/macos/daemon/macosroutemonitor.h @@ -5,13 +5,14 @@ #ifndef MACOSROUTEMONITOR_H #define MACOSROUTEMONITOR_H -#include "ipaddressrange.h" - #include +#include #include #include #include +#include "ipaddress.h" + struct if_msghdr; struct rt_msghdr; struct sockaddr; @@ -23,16 +24,21 @@ class MacosRouteMonitor final : public QObject { MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr); ~MacosRouteMonitor(); - bool insertRoute(const IPAddressRange& prefix); - bool deleteRoute(const IPAddressRange& prefix); + bool insertRoute(const IPAddress& prefix); + bool deleteRoute(const IPAddress& prefix); int interfaceFlags() { return m_ifflags; } + bool addExclusionRoute(const QHostAddress& address); + bool deleteExclusionRoute(const QHostAddress& address); + void flushExclusionRoutes(); + private: - void handleRtmAdd(const struct rt_msghdr* msg, const QByteArray& payload); void handleRtmDelete(const struct rt_msghdr* msg, const QByteArray& payload); - void handleRtmChange(const struct rt_msghdr* msg, const QByteArray& payload); + void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload); void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload); - bool rtmSendRoute(int action, const IPAddressRange& prefix); + bool rtmSendRoute(int action, const QHostAddress& prefix, unsigned int plen, + unsigned int ifindex, const void* gateway); + bool rtmFetchRoutes(int family); static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr, const void* sa); static QList parseAddrList(const QByteArray& data); @@ -44,7 +50,14 @@ class MacosRouteMonitor final : public QObject { static QString addrToString(const struct sockaddr* sa); static QString addrToString(const QByteArray& data); + QList m_exclusionRoutes; + QByteArray m_defaultGatewayIpv4; + QByteArray m_defaultGatewayIpv6; + unsigned int m_defaultIfindexIpv4 = 0; + unsigned int m_defaultIfindexIpv6 = 0; + QString m_ifname; + unsigned int m_ifindex = 0; int m_ifflags = 0; int m_rtsock = -1; int m_rtseq = 0; diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 017e1e41..eaaf6dec 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -3,8 +3,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wireguardutilsmacos.h" -#include "leakdetector.h" -#include "logger.h" + +#include #include #include @@ -12,19 +12,20 @@ #include #include -#include +#include "leakdetector.h" +#include "logger.h" constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard"; namespace { -Logger logger(LOG_MACOS, "WireguardUtilsMacos"); -Logger logwireguard(LOG_MACOS, "WireguardGo"); +Logger logger("WireguardUtilsMacos"); +Logger logwireguard("WireguardGo"); }; // namespace WireguardUtilsMacos::WireguardUtilsMacos(QObject* parent) : WireguardUtils(parent), m_tunnel(this) { - MVPN_COUNT_CTOR(WireguardUtilsMacos); + MZ_COUNT_CTOR(WireguardUtilsMacos); logger.debug() << "WireguardUtilsMacos created."; connect(&m_tunnel, SIGNAL(readyReadStandardOutput()), this, @@ -34,7 +35,7 @@ WireguardUtilsMacos::WireguardUtilsMacos(QObject* parent) } WireguardUtilsMacos::~WireguardUtilsMacos() { - MVPN_COUNT_DTOR(WireguardUtilsMacos); + MZ_COUNT_DTOR(WireguardUtilsMacos); logger.debug() << "WireguardUtilsMacos destroyed."; } @@ -68,15 +69,12 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"); pe.insert("WG_TUN_NAME_FILE", wgNameFile); -#ifdef QT_DEBUG +#ifdef MZ_DEBUG pe.insert("LOG_LEVEL", "debug"); #endif m_tunnel.setProcessEnvironment(pe); QDir appPath(QCoreApplication::applicationDirPath()); - appPath.cdUp(); - appPath.cd("Resources"); - appPath.cd("utils"); QStringList wgArgs = {"-f", "utun"}; m_tunnel.start(appPath.filePath("wireguard-go"), wgArgs); if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) { @@ -99,6 +97,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { // Send a UAPI command to configure the interface QString message("set=1\n"); QByteArray privateKey = QByteArray::fromBase64(config.m_privateKey.toUtf8()); + QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; @@ -134,14 +133,15 @@ bool WireguardUtilsMacos::deleteInterface() { // dummy implementations for now bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { - QByteArray publicKey = - QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); + QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); + QByteArray pskKey = QByteArray::fromBase64(qPrintable(config.m_serverPskKey)); // Update/create the peer config QString message; QTextStream out(&message); out << "set=1\n"; out << "public_key=" << QString(publicKey.toHex()) << "\n"; + out << "preshared_key=" << QString(pskKey.toHex()) << "\n"; if (!config.m_serverIpv4AddrIn.isNull()) { out << "endpoint=" << config.m_serverIpv4AddrIn << ":"; } else if (!config.m_serverIpv6AddrIn.isNull()) { @@ -153,10 +153,12 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { out << config.m_serverPort << "\n"; out << "replace_allowed_ips=true\n"; - for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) { + out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n"; + for (const IPAddress& ip : config.m_allowedIPAddressRanges) { out << "allowed_ip=" << ip.toString() << "\n"; } + logger.debug() << message; int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Peer configuration failed:" << strerror(err); @@ -164,8 +166,9 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { return (err == 0); } -bool WireguardUtilsMacos::deletePeer(const QString& pubkey) { - QByteArray publicKey = QByteArray::fromBase64(qPrintable(pubkey)); +bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) { + QByteArray publicKey = + QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); QString message; QTextStream out(&message); @@ -180,13 +183,10 @@ bool WireguardUtilsMacos::deletePeer(const QString& pubkey) { return (err == 0); } -WireguardUtils::peerStatus WireguardUtilsMacos::getPeerStatus( - const QString& pubkey) { - peerStatus status = {0, 0}; - QString hexkey = QByteArray::fromBase64(pubkey.toUtf8()).toHex(); +QList WireguardUtilsMacos::getPeerStatus() { QString reply = uapiCommand("get=1"); - bool match = false; - + PeerStatus status; + QList peerList; for (const QString& line : reply.split('\n')) { int eq = line.indexOf('='); if (eq <= 0) { @@ -196,39 +196,90 @@ WireguardUtils::peerStatus WireguardUtilsMacos::getPeerStatus( QString value = line.mid(eq + 1); if (name == "public_key") { - match = (value == hexkey); - continue; - } else if (!match) { - continue; + if (!status.m_pubkey.isEmpty()) { + peerList.append(status); + } + QByteArray pubkey = QByteArray::fromHex(value.toUtf8()); + status = PeerStatus(pubkey.toBase64()); } if (name == "tx_bytes") { - status.txBytes = value.toDouble(); + status.m_txBytes = value.toDouble(); } if (name == "rx_bytes") { - status.rxBytes = value.toDouble(); + status.m_rxBytes = value.toDouble(); + } + if (name == "last_handshake_time_sec") { + status.m_handshake += value.toLongLong() * 1000; + } + if (name == "last_handshake_time_nsec") { + status.m_handshake += value.toLongLong() / 1000000; } } + if (!status.m_pubkey.isEmpty()) { + peerList.append(status); + } - return status; + return peerList; } -bool WireguardUtilsMacos::updateRoutePrefix(const IPAddressRange& prefix, +bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix, int hopindex) { Q_UNUSED(hopindex); if (!m_rtmonitor) { return false; } - return m_rtmonitor->insertRoute(prefix); + if (prefix.prefixLength() > 0) { + return m_rtmonitor->insertRoute(prefix); + } + + // Ensure that we do not replace the default route. + if (prefix.type() == QAbstractSocket::IPv4Protocol) { + return m_rtmonitor->insertRoute(IPAddress("0.0.0.0/1")) && + m_rtmonitor->insertRoute(IPAddress("128.0.0.0/1")); + } + if (prefix.type() == QAbstractSocket::IPv6Protocol) { + return m_rtmonitor->insertRoute(IPAddress("::/1")) && + m_rtmonitor->insertRoute(IPAddress("8000::/1")); + } + + return false; } -bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddressRange& prefix, +bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix, int hopindex) { Q_UNUSED(hopindex); if (!m_rtmonitor) { return false; } - return m_rtmonitor->deleteRoute(prefix); + if (prefix.prefixLength() > 0) { + return m_rtmonitor->insertRoute(prefix); + } + + // Ensure that we do not replace the default route. + if (prefix.type() == QAbstractSocket::IPv4Protocol) { + return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) && + m_rtmonitor->deleteRoute(IPAddress("128.0.0.0/1")); + } else if (prefix.type() == QAbstractSocket::IPv6Protocol) { + return m_rtmonitor->deleteRoute(IPAddress("::/1")) && + m_rtmonitor->deleteRoute(IPAddress("8000::/1")); + } else { + return false; + } +} + +bool WireguardUtilsMacos::addExclusionRoute(const QHostAddress& address) { + if (!m_rtmonitor) { + return false; + } + return m_rtmonitor->addExclusionRoute(address); +} + +bool WireguardUtilsMacos::deleteExclusionRoute(const QHostAddress& address) { + if (!m_rtmonitor) { + return false; + } + return m_rtmonitor->deleteExclusionRoute(address); } QString WireguardUtilsMacos::uapiCommand(const QString& command) { diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.h b/client/platforms/macos/daemon/wireguardutilsmacos.h index 810d04a7..ba830c1c 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.h +++ b/client/platforms/macos/daemon/wireguardutilsmacos.h @@ -5,12 +5,12 @@ #ifndef WIREGUARDUTILSMACOS_H #define WIREGUARDUTILSMACOS_H -#include "daemon/wireguardutils.h" -#include "macosroutemonitor.h" - #include #include +#include "daemon/wireguardutils.h" +#include "macosroutemonitor.h" + class WireguardUtilsMacos final : public WireguardUtils { Q_OBJECT @@ -26,11 +26,14 @@ class WireguardUtilsMacos final : public WireguardUtils { bool deleteInterface() override; bool updatePeer(const InterfaceConfig& config) override; - bool deletePeer(const QString& pubkey) override; - peerStatus getPeerStatus(const QString& pubkey) override; + bool deletePeer(const InterfaceConfig& config) override; + QList getPeerStatus() override; - bool updateRoutePrefix(const IPAddressRange& prefix, int hopindex) override; - bool deleteRoutePrefix(const IPAddressRange& prefix, int hopindex) override; + bool updateRoutePrefix(const IPAddress& prefix, int hopindex) override; + bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) override; + + bool addExclusionRoute(const QHostAddress& address) override; + bool deleteExclusionRoute(const QHostAddress& address) override; signals: void backendFailure(); diff --git a/client/platforms/macos/macoscryptosettings.mm b/client/platforms/macos/macoscryptosettings.mm deleted file mode 100644 index a6c694e7..00000000 --- a/client/platforms/macos/macoscryptosettings.mm +++ /dev/null @@ -1,136 +0,0 @@ -/* 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 "cryptosettings.h" -#include "logger.h" - -#include - -constexpr const NSString* SERVICE = @"Mozilla VPN"; - -#import - -namespace { - -Logger logger({LOG_MACOS, LOG_MAIN}, "MacOSCryptoSettings"); - -bool initialized = false; -QByteArray key; - -} // anonymous - -// static -void CryptoSettings::resetKey() { - logger.debug() << "Reset the key in the keychain"; - - NSData* service = [SERVICE dataUsingEncoding:NSUTF8StringEncoding]; - - NSString* appId = [[NSBundle mainBundle] bundleIdentifier]; - - NSMutableDictionary* query = [[NSMutableDictionary alloc] init]; - - [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; - [query setObject:service forKey:(id)kSecAttrGeneric]; - [query setObject:service forKey:(id)kSecAttrAccount]; - [query setObject:appId forKey:(id)kSecAttrService]; - - SecItemDelete((CFDictionaryRef)query); - - [query release]; - - initialized = false; -} - -// static -bool CryptoSettings::getKey(uint8_t output[CRYPTO_SETTINGS_KEY_SIZE]) { -#if defined(MVPN_IOS) || defined(MVPN_MACOS_NETWORKEXTENSION) || defined(MVPN_MACOS_DAEMON) - if (!initialized) { - initialized = true; - - logger.debug() << "Retrieving the key from the keychain"; - - NSData* service = [SERVICE dataUsingEncoding:NSUTF8StringEncoding]; - - NSString* appId = [[NSBundle mainBundle] bundleIdentifier]; - NSMutableDictionary* query = [[NSMutableDictionary alloc] init]; - - [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; - [query setObject:service forKey:(id)kSecAttrGeneric]; - [query setObject:service forKey:(id)kSecAttrAccount]; - [query setObject:appId forKey:(id)kSecAttrService]; - - [query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; - [query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; - - NSData* keyData = NULL; - OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)(void*)&keyData); - [query release]; - - if (status == noErr) { - key = QByteArray::fromNSData(keyData); - - logger.debug() << "Key found with length:" << key.length(); - if (key.length() == CRYPTO_SETTINGS_KEY_SIZE) { - memcpy(output, key.data(), CRYPTO_SETTINGS_KEY_SIZE); - return true; - } - } - - logger.warning() << "Key not found. Let's create it. Error:" << status; - key = QByteArray(CRYPTO_SETTINGS_KEY_SIZE, 0x00); - QRandomGenerator* rg = QRandomGenerator::system(); - for (int i = 0; i < CRYPTO_SETTINGS_KEY_SIZE; ++i) { - key[i] = rg->generate() & 0xFF; - } - - query = [[NSMutableDictionary alloc] init]; - - [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; - [query setObject:service forKey:(id)kSecAttrGeneric]; - [query setObject:service forKey:(id)kSecAttrAccount]; - [query setObject:appId forKey:(id)kSecAttrService]; - - SecItemDelete((CFDictionaryRef)query); - - keyData = key.toNSData(); - [query setObject:keyData forKey:(id)kSecValueData]; - - status = SecItemAdd((CFDictionaryRef)query, NULL); - - if (status != noErr) { - logger.error() << "Failed to store the key. Error:" << status; - key = QByteArray(); - } - - [query release]; - } - - if (key.length() == CRYPTO_SETTINGS_KEY_SIZE) { - memcpy(output, key.data(), CRYPTO_SETTINGS_KEY_SIZE); - return true; - } - - logger.error() << "Invalid key"; -#else - Q_UNUSED(output); -#endif - - return false; -} - -// static -CryptoSettings::Version CryptoSettings::getSupportedVersion() { - logger.debug() << "Get supported settings method"; - -#if defined(MVPN_IOS) || defined(MVPN_MACOS_NETWORKEXTENSION) || defined(MVPN_MACOS_DAEMON) - uint8_t key[CRYPTO_SETTINGS_KEY_SIZE]; - if (getKey(key)) { - logger.debug() << "Encryption supported!"; - return CryptoSettings::EncryptionChachaPolyV1; - } -#endif - - logger.debug() << "No encryption"; - return CryptoSettings::NoEncryption; -} diff --git a/client/platforms/macos/macosmenubar.cpp b/client/platforms/macos/macosmenubar.cpp deleted file mode 100644 index 77694556..00000000 --- a/client/platforms/macos/macosmenubar.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* 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 "macosmenubar.h" -#include "leakdetector.h" -#include "logger.h" -#include "mozillavpn.h" -#include "qmlengineholder.h" -#ifdef MVPN_MACOS -# include "platforms/macos/macosutils.h" -#endif - -#include -#include -#include - -namespace { -Logger logger(LOG_MACOS, "MacOSManuBar"); -MacOSMenuBar* s_instance = nullptr; -} // namespace - -MacOSMenuBar::MacOSMenuBar() { - MVPN_COUNT_CTOR(MacOSMenuBar); - - Q_ASSERT(!s_instance); - s_instance = this; -} - -MacOSMenuBar::~MacOSMenuBar() { - MVPN_COUNT_DTOR(MacOSMenuBar); - - Q_ASSERT(s_instance == this); - s_instance = nullptr; -} - -// static -MacOSMenuBar* MacOSMenuBar::instance() { - Q_ASSERT(s_instance); - return s_instance; -} - -void MacOSMenuBar::initialize() { - logger.debug() << "Creating menubar"; - - AmneziaVPN* vpn = AmneziaVPN::instance(); - - m_menuBar = new QMenuBar(nullptr); - - //% "File" - QMenu* fileMenu = m_menuBar->addMenu(qtTrId("menubar.file.title")); - - // Do not use qtTrId here! - QAction* quit = - fileMenu->addAction("quit", vpn->controller(), &Controller::quit); - quit->setMenuRole(QAction::QuitRole); - - // Do not use qtTrId here! - m_aboutAction = - fileMenu->addAction("about.vpn", vpn, &AmneziaVPN::requestAbout); - m_aboutAction->setMenuRole(QAction::AboutRole); - m_aboutAction->setVisible(vpn->state() == AmneziaVPN::StateMain); - - // Do not use qtTrId here! - m_preferencesAction = - fileMenu->addAction("preferences", vpn, &AmneziaVPN::requestSettings); - m_preferencesAction->setMenuRole(QAction::PreferencesRole); - m_preferencesAction->setVisible(vpn->state() == AmneziaVPN::StateMain); - - m_closeAction = fileMenu->addAction("close", []() { - QmlEngineHolder::instance()->hideWindow(); -#ifdef MVPN_MACOS - MacOSUtils::hideDockIcon(); -#endif - }); - m_closeAction->setShortcut(QKeySequence::Close); - - m_helpMenu = m_menuBar->addMenu(""); - - retranslate(); -}; - -void MacOSMenuBar::controllerStateChanged() { - AmneziaVPN* vpn = AmneziaVPN::instance(); - m_preferencesAction->setVisible(vpn->state() == AmneziaVPN::StateMain); - m_aboutAction->setVisible(vpn->state() == AmneziaVPN::StateMain); -} - -void MacOSMenuBar::retranslate() { - logger.debug() << "Retranslate"; - - //% "Close" - m_closeAction->setText(qtTrId("menubar.file.close")); - - //% "Help" - m_helpMenu->setTitle(qtTrId("menubar.help.title")); - for (QAction* action : m_helpMenu->actions()) { - m_helpMenu->removeAction(action); - } - - AmneziaVPN* vpn = AmneziaVPN::instance(); - vpn->helpModel()->forEach([&](const char* nameId, int id) { - m_helpMenu->addAction(qtTrId(nameId), - [help = vpn->helpModel(), id]() { help->open(id); }); - }); -} diff --git a/client/platforms/macos/macosmenubar.h b/client/platforms/macos/macosmenubar.h deleted file mode 100644 index a95ecd39..00000000 --- a/client/platforms/macos/macosmenubar.h +++ /dev/null @@ -1,42 +0,0 @@ -/* 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 MACOSMENUBAR_H -#define MACOSMENUBAR_H - -#include - -class QAction; -class QMenu; -class QMenuBar; - -class MacOSMenuBar final : public QObject { - Q_OBJECT - Q_DISABLE_COPY_MOVE(MacOSMenuBar) - - public: - MacOSMenuBar(); - ~MacOSMenuBar(); - - static MacOSMenuBar* instance(); - - void initialize(); - - void retranslate(); - - QMenuBar* menuBar() const { return m_menuBar; } - - public slots: - void controllerStateChanged(); - - private: - QMenuBar* m_menuBar = nullptr; - - QAction* m_aboutAction = nullptr; - QAction* m_preferencesAction = nullptr; - QAction* m_closeAction = nullptr; - QMenu* m_helpMenu = nullptr; -}; - -#endif // MACOSMENUBAR_H diff --git a/client/platforms/macos/macosnetworkwatcher.h b/client/platforms/macos/macosnetworkwatcher.h index a35a096d..2ae4cfd7 100644 --- a/client/platforms/macos/macosnetworkwatcher.h +++ b/client/platforms/macos/macosnetworkwatcher.h @@ -5,19 +5,24 @@ #ifndef MACOSNETWORKWATCHER_H #define MACOSNETWORKWATCHER_H +#import + +#include "../ios/iosnetworkwatcher.h" #include "networkwatcherimpl.h" -class MacOSNetworkWatcher final : public NetworkWatcherImpl { +class QString; + +class MacOSNetworkWatcher final : public IOSNetworkWatcher { public: MacOSNetworkWatcher(QObject* parent); ~MacOSNetworkWatcher(); - void initialize() override; - void start() override; void checkInterface(); + void controllerStateChanged(); + private: void* m_delegate = nullptr; }; diff --git a/client/platforms/macos/macosnetworkwatcher.mm b/client/platforms/macos/macosnetworkwatcher.mm index 21b10710..3b4158a4 100644 --- a/client/platforms/macos/macosnetworkwatcher.mm +++ b/client/platforms/macos/macosnetworkwatcher.mm @@ -7,9 +7,10 @@ #include "logger.h" #import +#import namespace { -Logger logger(LOG_MACOS, "MacOSNetworkWatcher"); +Logger logger("MacOSNetworkWatcher"); } @interface MacOSNetworkWatcherDelegate : NSObject { @@ -37,13 +38,12 @@ Logger logger(LOG_MACOS, "MacOSNetworkWatcher"); @end -MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) { - MVPN_COUNT_CTOR(MacOSNetworkWatcher); +MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent) { + MZ_COUNT_CTOR(MacOSNetworkWatcher); } MacOSNetworkWatcher::~MacOSNetworkWatcher() { - MVPN_COUNT_DTOR(MacOSNetworkWatcher); - + MZ_COUNT_DTOR(MacOSNetworkWatcher); if (m_delegate) { CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; if (!client) { @@ -57,10 +57,6 @@ MacOSNetworkWatcher::~MacOSNetworkWatcher() { } } -void MacOSNetworkWatcher::initialize() { - // Nothing to do here -} - void MacOSNetworkWatcher::start() { NetworkWatcherImpl::start(); @@ -129,3 +125,4 @@ void MacOSNetworkWatcher::checkInterface() { logger.debug() << "Secure WiFi interface"; } + diff --git a/client/platforms/macos/macospingsender.cpp b/client/platforms/macos/macospingsender.cpp index 04090435..3b5a09b0 100644 --- a/client/platforms/macos/macospingsender.cpp +++ b/client/platforms/macos/macospingsender.cpp @@ -3,10 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macospingsender.h" -#include "leakdetector.h" -#include "logger.h" - -#include #include #include @@ -14,19 +10,26 @@ #include #include #include +#include #include +#include +#include + +#include "leakdetector.h" +#include "logger.h" + namespace { -Logger logger({LOG_MACOS, LOG_NETWORKING}, "MacOSPingSender"); +Logger logger("MacOSPingSender"); int identifier() { return (getpid() & 0xFFFF); } }; // namespace -MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent) +MacOSPingSender::MacOSPingSender(const QHostAddress& source, QObject* parent) : PingSender(parent) { - MVPN_COUNT_CTOR(MacOSPingSender); + MZ_COUNT_CTOR(MacOSPingSender); if (getuid()) { m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); @@ -38,15 +41,15 @@ MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent) return; } + quint32 ipv4addr = INADDR_ANY; + if (!source.isNull()) { + ipv4addr = source.toIPv4Address(); + } struct sockaddr_in addr; bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_len = sizeof(addr); - - if (inet_aton(source.toLocal8Bit().constData(), &addr.sin_addr) == 0) { - logger.error() << "source address error"; - return; - } + addr.sin_addr.s_addr = qToBigEndian(ipv4addr); if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) { logger.error() << "bind error:" << strerror(errno); @@ -59,22 +62,19 @@ MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent) } MacOSPingSender::~MacOSPingSender() { - MVPN_COUNT_DTOR(MacOSPingSender); + MZ_COUNT_DTOR(MacOSPingSender); if (m_socket >= 0) { close(m_socket); } } -void MacOSPingSender::sendPing(const QString& dest, quint16 sequence) { +void MacOSPingSender::sendPing(const QHostAddress& dest, quint16 sequence) { + quint32 ipv4dest = dest.toIPv4Address(); struct sockaddr_in addr; bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_len = sizeof(addr); - - if (inet_aton(dest.toLocal8Bit().constData(), &addr.sin_addr) == 0) { - logger.error() << "DNS lookup failed"; - return; - } + addr.sin_addr.s_addr = qToBigEndian(ipv4dest); struct icmp packet; bzero(&packet, sizeof packet); @@ -86,6 +86,7 @@ void MacOSPingSender::sendPing(const QString& dest, quint16 sequence) { if (sendto(m_socket, (char*)&packet, sizeof(packet), 0, (struct sockaddr*)&addr, sizeof(addr)) != sizeof(packet)) { logger.error() << "ping sending failed:" << strerror(errno); + emit criticalPingError(); return; } } diff --git a/client/platforms/macos/macospingsender.h b/client/platforms/macos/macospingsender.h index cc54c613..73b20409 100644 --- a/client/platforms/macos/macospingsender.h +++ b/client/platforms/macos/macospingsender.h @@ -14,10 +14,10 @@ class MacOSPingSender final : public PingSender { Q_DISABLE_COPY_MOVE(MacOSPingSender) public: - MacOSPingSender(const QString& source, QObject* parent = nullptr); + MacOSPingSender(const QHostAddress& source, QObject* parent = nullptr); ~MacOSPingSender(); - void sendPing(const QString& dest, quint16 sequence) override; + void sendPing(const QHostAddress& dest, quint16 sequence) override; private slots: void socketReady(); diff --git a/client/platforms/macos/macosstartatbootwatcher.cpp b/client/platforms/macos/macosstartatbootwatcher.cpp deleted file mode 100644 index e8c504a3..00000000 --- a/client/platforms/macos/macosstartatbootwatcher.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/* 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 "macosstartatbootwatcher.h" -#include "leakdetector.h" -#include "logger.h" -#include "macosutils.h" - -namespace { -Logger logger(LOG_MACOS, "MacOSStartAtBootWatcher"); -} - -MacOSStartAtBootWatcher::MacOSStartAtBootWatcher(bool startAtBoot) { - MVPN_COUNT_CTOR(MacOSStartAtBootWatcher); - - logger.debug() << "StartAtBoot watcher"; - MacOSUtils::enableLoginItem(startAtBoot); -} - -MacOSStartAtBootWatcher::~MacOSStartAtBootWatcher() { - MVPN_COUNT_DTOR(MacOSStartAtBootWatcher); -} - -void MacOSStartAtBootWatcher::startAtBootChanged(bool startAtBoot) { - logger.debug() << "StartAtBoot changed:" << startAtBoot; - MacOSUtils::enableLoginItem(startAtBoot); -} diff --git a/client/platforms/macos/macosstartatbootwatcher.h b/client/platforms/macos/macosstartatbootwatcher.h deleted file mode 100644 index 18fd540a..00000000 --- a/client/platforms/macos/macosstartatbootwatcher.h +++ /dev/null @@ -1,22 +0,0 @@ -/* 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 MACOSSTARTATBOOTWATCHER_H -#define MACOSSTARTATBOOTWATCHER_H - -#include - -class MacOSStartAtBootWatcher final : public QObject { - Q_OBJECT - Q_DISABLE_COPY_MOVE(MacOSStartAtBootWatcher) - - public: - explicit MacOSStartAtBootWatcher(bool startAtBoot); - ~MacOSStartAtBootWatcher(); - - public slots: - void startAtBootChanged(bool value); -}; - -#endif // MACOSSTARTATBOOTWATCHER_H diff --git a/client/platforms/macos/macosstatusicon.h b/client/platforms/macos/macosstatusicon.h new file mode 100644 index 00000000..3a69dff4 --- /dev/null +++ b/client/platforms/macos/macosstatusicon.h @@ -0,0 +1,27 @@ +/* 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 MACOSSTATUSICON_H +#define MACOSSTATUSICON_H + +#include +#include + +class MacOSStatusIcon final : public QObject { + Q_OBJECT + Q_DISABLE_COPY_MOVE(MacOSStatusIcon) + + public: + explicit MacOSStatusIcon(QObject* parent); + ~MacOSStatusIcon(); + + public: + void setIcon(const QString& iconUrl); + void setIndicatorColor(const QColor& indicatorColor); + void setMenu(NSMenu* statusBarMenu); + void setToolTip(const QString& tooltip); + void showMessage(const QString& title, const QString& message); +}; + +#endif // MACOSSTATUSICON_H diff --git a/client/platforms/macos/macosstatusicon.mm b/client/platforms/macos/macosstatusicon.mm new file mode 100644 index 00000000..cea8439c --- /dev/null +++ b/client/platforms/macos/macosstatusicon.mm @@ -0,0 +1,204 @@ +/* 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 "macosstatusicon.h" +#include "leakdetector.h" +#include "logger.h" + +#import +#import +#import + +/** + * Creates a NSStatusItem with that can hold an icon. Additionally a NSView is + * set as a subview to the button item of the status item. The view serves as + * an indicator that can be displayed in color eventhough the icon is set as a + * template. In that way we give the system control over it’s effective + * appearance. + */ +@interface MacOSStatusIconDelegate : NSObject +@property(assign) NSStatusItem* statusItem; +@property(assign) NSView* statusIndicator; + +- (void)setIcon:(NSData*)imageData; +- (void)setIndicator; +- (void)setIndicatorColor:(NSColor*)color; +- (void)setMenu:(NSMenu*)statusBarMenu; +- (void)setToolTip:(NSString*)tooltip; +@end + +@implementation MacOSStatusIconDelegate +/** + * Initializes and sets the status item and indicator objects. + * + * @return An instance of MacOSStatusIconDelegate. + */ +- (id)init { + self = [super init]; + + // Create status item + self.statusItem = + [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; + self.statusItem.visible = true; + // Add the indicator as a subview + [self setIndicator]; + + return self; +} + +/** + * Sets the image for the status icon. + * + * @param iconPath The data for the icon image. + */ +- (void)setIcon:(NSData*)imageData { + NSImage* image = [[NSImage alloc] initWithData:imageData]; + [image setTemplate:true]; + + [self.statusItem.button setImage:image]; + [image release]; +} + +/** + * Adds status indicator as a subview to the status item button. + */ +- (void)setIndicator { + float viewHeight = NSHeight([self.statusItem.button bounds]); + float dotSize = viewHeight * 0.35; + float dotOrigin = (viewHeight - dotSize) * 0.8; + + NSView* dot = [[NSView alloc] initWithFrame:NSMakeRect(dotOrigin, dotOrigin, dotSize, dotSize)]; + self.statusIndicator = dot; + self.statusIndicator.wantsLayer = true; + self.statusIndicator.layer.cornerRadius = dotSize * 0.5; + + [self.statusItem.button addSubview:self.statusIndicator]; + [dot release]; +} + +/** + * Sets the color if the indicator. + * + * @param color The indicator background color. + */ +- (void)setIndicatorColor:(NSColor*)color { + if (self.statusIndicator) { + self.statusIndicator.layer.backgroundColor = color.CGColor; + } +} + +/** + * Sets the status bar menu to the status item. + * + * @param statusBarMenu The menu object that is passed from QT. + */ +- (void)setMenu:(NSMenu*)statusBarMenu { + [self.statusItem setMenu:statusBarMenu]; +} + +/** + * Sets the tooltip string for the status item. + * + * @param tooltip The tooltip string. + */ +- (void)setToolTip:(NSString*)tooltip { + [self.statusItem.button setToolTip:tooltip]; +} +@end + +namespace { +Logger logger("MacOSStatusIcon"); + +MacOSStatusIconDelegate* m_statusBarIcon = nullptr; +} + +MacOSStatusIcon::MacOSStatusIcon(QObject* parent) : QObject(parent) { + MZ_COUNT_CTOR(MacOSStatusIcon); + + logger.debug() << "Register delegate"; + Q_ASSERT(!m_statusBarIcon); + + m_statusBarIcon = [[MacOSStatusIconDelegate alloc] init]; +} + +MacOSStatusIcon::~MacOSStatusIcon() { + MZ_COUNT_DTOR(MacOSStatusIcon); + + logger.debug() << "Remove delegate"; + Q_ASSERT(m_statusBarIcon); + + [static_cast(m_statusBarIcon) dealloc]; + m_statusBarIcon = nullptr; +} + +void MacOSStatusIcon::setIcon(const QString& iconPath) { + logger.debug() << "Set icon" << iconPath; + + QResource imageResource = QResource(iconPath); + Q_ASSERT(imageResource.isValid()); + + [m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()]; +} + +void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) { + logger.debug() << "Set indicator color"; + + if (!indicatorColor.isValid()) { + [m_statusBarIcon setIndicatorColor:[NSColor clearColor]]; + return; + } + + NSColor* color = [NSColor colorWithCalibratedRed:indicatorColor.red() / 255.0f + green:indicatorColor.green() / 255.0f + blue:indicatorColor.blue() / 255.0f + alpha:indicatorColor.alpha() / 255.0f]; + [m_statusBarIcon setIndicatorColor:color]; +} + +void MacOSStatusIcon::setMenu(NSMenu* statusBarMenu) { + logger.debug() << "Set menu"; + [m_statusBarIcon setMenu:statusBarMenu]; +} + +void MacOSStatusIcon::setToolTip(const QString& tooltip) { + logger.debug() << "Set tooltip"; + [m_statusBarIcon setToolTip:tooltip.toNSString()]; +} + +void MacOSStatusIcon::showMessage(const QString& title, const QString& message) { + logger.debug() << "Show message"; + + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; + + // This is a no-op is authorization has been granted. + [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | + UNAuthorizationOptionBadge) + completionHandler:^(BOOL granted, NSError* _Nullable error) { + if (error) { + // Note: This error may happen if the application is not signed. + NSLog(@"Error asking for permission to send notifications %@", error); + return; + } + }]; + + UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; + + content.title = [title.toNSString() autorelease]; + content.body = [message.toNSString() autorelease]; + content.sound = [UNNotificationSound defaultSound]; + + UNTimeIntervalNotificationTrigger* trigger = + [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO]; + + UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn" + content:content + trigger:trigger]; + + [center addNotificationRequest:request + withCompletionHandler:^(NSError* _Nullable error) { + if (error) { + logger.error() << "Local Notification failed" << error; + } + }]; +} diff --git a/client/platforms/macos/macosutils.h b/client/platforms/macos/macosutils.h index 7ef16be5..63ba98f4 100644 --- a/client/platforms/macos/macosutils.h +++ b/client/platforms/macos/macosutils.h @@ -10,14 +10,19 @@ class MacOSUtils final { public: + static NSString* appId(); + static QString computerName(); static void enableLoginItem(bool startAtBoot); static void setDockClickHandler(); + static void setStatusBarTextColor(); static void hideDockIcon(); static void showDockIcon(); + + static void patchNSStatusBarSetImageForBigSur(); }; #endif // MACOSUTILS_H diff --git a/client/platforms/macos/macosutils.mm b/client/platforms/macos/macosutils.mm index 7fd7c86e..a704f428 100644 --- a/client/platforms/macos/macosutils.mm +++ b/client/platforms/macos/macosutils.mm @@ -4,20 +4,27 @@ #include "macosutils.h" #include "logger.h" -#include "models/helpmodel.h" -#include "qmlengineholder.h" #include #include -#include -#include - #import #import namespace { -Logger logger(LOG_MACOS, "MacOSUtils"); +Logger logger("MacOSUtils"); +} + +// static +NSString* MacOSUtils::appId() { + NSString* appId = [[NSBundle mainBundle] bundleIdentifier]; + if (!appId) { + // Fallback. When an unsigned/un-notarized app is executed in + // command-line mode, it could fail the fetching of its own bundle id. + appId = @"org.amnezia.AmneziaVPN"; + } + + return appId; } // static @@ -30,7 +37,9 @@ QString MacOSUtils::computerName() { void MacOSUtils::enableLoginItem(bool startAtBoot) { logger.debug() << "Enabling login-item"; - NSString* appId = [[NSBundle mainBundle] bundleIdentifier]; + NSString* appId = MacOSUtils::appId(); + Q_ASSERT(appId); + NSString* loginItemAppId = QString("%1.login-item").arg(QString::fromNSString(appId)).toNSString(); CFStringRef cfs = (__bridge CFStringRef)loginItemAppId; @@ -46,7 +55,8 @@ bool dockClickHandler(id self, SEL cmd, ...) { Q_UNUSED(cmd); logger.debug() << "Dock icon clicked."; - QmlEngineHolder::instance()->showWindow(); + //TODO IMPL FOR AMNEZIA + //QmlEngineHolder::instance()->showWindow(); return FALSE; } @@ -89,3 +99,102 @@ void MacOSUtils::hideDockIcon() { void MacOSUtils::showDockIcon() { [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; } + +/** + * Replace the setImage method on NSStatusBarButton with a method that scales + * images proportionally before setting. + * + * The reason for this is that there is a bug in Qt 5.15 that causes status bar + * icons to be displayed larger than UI recommendations, and out of proportion + * on displays with a device pixel ratio greater than 1 (MacOS Big Sur only). + * This bug will not be fixed in Qt open source versions, so we have to resort + * to a hack that exchanges the implementation of a method on NSStatusBarButton + * with one that correctly scales the icon. + * + * Original bug (and sample implementation): + * https://bugreports.qt.io/browse/QTBUG-88600 + */ +void MacOSUtils::patchNSStatusBarSetImageForBigSur() { + Method original = class_getInstanceMethod([NSStatusBarButton class], @selector(setImage:)); + Method patched = class_getInstanceMethod([NSStatusBarButton class], @selector(setImagePatched:)); + method_exchangeImplementations(original, patched); +} + +@interface NSImageScalingHelper : NSObject +/** + * Create a proportionally scaled image according to the given target size. + * + * @param sourceImage The original image to be scaled. + * @param targetSize The required size of the image. + * @return A scaled image. + */ ++ (NSImage*)imageByScaling:(NSImage*)sourceImage size:(NSSize)targetSize; +@end + +@implementation NSImageScalingHelper ++ (NSImage*)imageByScaling:(NSImage*)sourceImage size:(NSSize)targetSize { + NSImage* newImage = nil; + + if ([sourceImage isValid]) { + NSSize sourceSize = [sourceImage size]; + + if (sourceSize.width != 0.0 && sourceSize.height != 0.0) { + float scaleFactor = 0.0; + float scaledWidth = targetSize.width; + float scaledHeight = targetSize.height; + + NSPoint thumbnailPoint = NSZeroPoint; + + if (NSEqualSizes(sourceSize, targetSize) == NO) { + float widthFactor = targetSize.width / sourceSize.width; + float heightFactor = targetSize.height / sourceSize.height; + + if (widthFactor < heightFactor) { + scaleFactor = widthFactor; + } else { + scaleFactor = heightFactor; + } + scaledWidth = sourceSize.width * scaleFactor; + scaledHeight = sourceSize.height * scaleFactor; + + if (widthFactor < heightFactor) { + thumbnailPoint.y = (targetSize.height - scaledHeight) * 0.5; + } else { + thumbnailPoint.x = (targetSize.width - scaledWidth) * 0.5; + } + } + + newImage = [[NSImage alloc] initWithSize:targetSize]; + + [newImage lockFocus]; + + NSRect thumbnailRect; + thumbnailRect.origin = thumbnailPoint; + thumbnailRect.size.width = scaledWidth; + thumbnailRect.size.height = scaledHeight; + [sourceImage drawInRect:thumbnailRect + fromRect:NSZeroRect + operation:NSCompositingOperationSourceOver + fraction:1.0]; + + [newImage unlockFocus]; + + [newImage setTemplate:[sourceImage isTemplate]]; + } + } + return [newImage autorelease]; +} +@end + +@implementation NSStatusBarButton (Swizzle) +- (void)setImagePatched:(NSImage*)image { + NSImage* img = image; + + if (image != nil) { + int thickness = [[NSStatusBar systemStatusBar] thickness]; + img = [NSImageScalingHelper imageByScaling:image size:NSMakeSize(thickness, thickness)]; + } + + [self setImagePatched:img]; +} +@end diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 549c3774..63351311 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -53,16 +53,6 @@ QString ProtocolProps::transportProtoToString(TransportProto proto, Proto p) QMetaEnum metaEnum = QMetaEnum::fromType(); QString protoKey = metaEnum.valueToKey(static_cast(proto)); return protoKey.toLower(); - -// if (p == Protocol::OpenVpn){ -// return protoKey.toLower(); -// } -// else if (p == Protocol::ShadowSocks) { -// return protoKey.toUpper(); -// } -// else { -// return protoKey.toLower(); -// } } diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 1f890f4c..f7421c2d 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -1,9 +1,10 @@ #ifndef PROTOCOLS_DEFS_H #define PROTOCOLS_DEFS_H +#include #include #include -#include + namespace amnezia { namespace config_key { @@ -199,32 +200,6 @@ public: }; -static void declareQmlProtocolEnum() { - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "ProtocolEnum", - "Error: only enums" - ); - - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "TransportProto", - "Error: only enums" - ); - - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "ServiceType", - "Error: only enums" - ); -} - } // namespace amnezia QDebug operator<<(QDebug debug, const amnezia::Proto &p); diff --git a/client/protocols/qml_register_protocols.h b/client/protocols/qml_register_protocols.h new file mode 100644 index 00000000..30b006ae --- /dev/null +++ b/client/protocols/qml_register_protocols.h @@ -0,0 +1,44 @@ +#ifndef QML_REGISTER_PROTOCOLS_H +#define QML_REGISTER_PROTOCOLS_H + +#include "protocols_defs.h" + +#include +#include +#include + +namespace amnezia { + +using namespace amnezia::ProtocolEnumNS; + +void declareQmlProtocolEnum() { + qmlRegisterUncreatableMetaObject( + ProtocolEnumNS::staticMetaObject, + "ProtocolEnum", + 1, 0, + "ProtocolEnum", + "Error: only enums" + ); + + qmlRegisterUncreatableMetaObject( + ProtocolEnumNS::staticMetaObject, + "ProtocolEnum", + 1, 0, + "TransportProto", + "Error: only enums" + ); + + qmlRegisterUncreatableMetaObject( + ProtocolEnumNS::staticMetaObject, + "ProtocolEnum", + 1, 0, + "ServiceType", + "Error: only enums" + ); +} + +} // namespace amnezia + +QDebug operator<<(QDebug debug, const amnezia::Proto &p); + +#endif // QML_REGISTER_PROTOCOLS_H diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 666bf80d..2ba2b218 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -8,10 +8,26 @@ #include "wireguardprotocol.h" #include "utilities.h" +#include "mozilla/localsocketcontroller.h" + WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) { m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); writeWireguardConfiguration(configuration); + + // MZ +#if defined(MZ_LINUX) + //m_impl.reset(new LinuxController()); +#elif defined(MZ_MACOS) // || defined(MZ_WINDOWS) + m_impl.reset(new LocalSocketController()); + connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) { + emit connectionStateChanged(VpnProtocol::Connected); + }); + connect(m_impl.get(), &ControllerImpl::disconnected, this, [this](){ + emit connectionStateChanged(VpnProtocol::Disconnected); + }); + m_impl->initialize(nullptr, nullptr); +#endif } WireguardProtocol::~WireguardProtocol() @@ -22,7 +38,11 @@ WireguardProtocol::~WireguardProtocol() void WireguardProtocol::stop() { -#ifndef Q_OS_IOS +#ifdef Q_OS_MAC + stopMzImpl(); + return; +#endif + if (!QFileInfo::exists(Utils::wireguardExecPath())) { qCritical() << "Wireguard executable missing!"; setLastError(ErrorCode::ExecutableMissing); @@ -76,9 +96,22 @@ void WireguardProtocol::stop() m_wireguardStopProcess->waitForFinished(10000); setConnectionState(VpnProtocol::Disconnected); -#endif } +#ifdef Q_OS_MAC +ErrorCode WireguardProtocol::startMzImpl() +{ + m_impl->activate(m_rawConfig); + return ErrorCode::NoError; +} + +ErrorCode WireguardProtocol::stopMzImpl() +{ + m_impl->deactivate(); + return ErrorCode::NoError; +} +#endif + void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration) { QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toObject(); @@ -131,13 +164,14 @@ void WireguardProtocol::updateRouteGateway(QString line) ErrorCode WireguardProtocol::start() { -#ifndef Q_OS_IOS if (!m_isConfigLoaded) { setLastError(ErrorCode::ConfigMissing); return lastError(); } - WireguardProtocol::stop(); +#ifdef Q_OS_MAC + return startMzImpl(); +#endif if (!QFileInfo::exists(Utils::wireguardExecPath())) { setLastError(ErrorCode::ExecutableMissing); @@ -212,27 +246,11 @@ ErrorCode WireguardProtocol::start() m_wireguardStartProcess->waitForFinished(10000); return ErrorCode::NoError; -#else - return ErrorCode::NotImplementedError; -#endif } void WireguardProtocol::updateVpnGateway(const QString &line) { -// // line looks like -// // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' -// QStringList params = line.split(","); -// for (const QString &l : params) { -// if (l.contains("ifconfig")) { -// if (l.split(" ").size() == 3) { -// m_vpnLocalAddress = l.split(" ").at(1); -// m_vpnGateway = l.split(" ").at(2); - -// qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); -// } -// } -// } } QString WireguardProtocol::serviceName() const @@ -247,7 +265,7 @@ QStringList WireguardProtocol::stopArgs() #elif defined Q_OS_LINUX return {"down", "wg99"}; #else - return {"--remove", configPath()}; + return {}; #endif } @@ -258,7 +276,7 @@ QStringList WireguardProtocol::startArgs() #elif defined Q_OS_LINUX return {"up", "wg99"}; #else - return {"--add", configPath()}; + return {}; #endif } diff --git a/client/protocols/wireguardprotocol.h b/client/protocols/wireguardprotocol.h index 880417b4..8f6bad9a 100644 --- a/client/protocols/wireguardprotocol.h +++ b/client/protocols/wireguardprotocol.h @@ -10,6 +10,8 @@ #include "vpnprotocol.h" #include "core/ipcclient.h" +#include "mozilla/controllerimpl.h" + class WireguardProtocol : public VpnProtocol { Q_OBJECT @@ -21,6 +23,11 @@ public: ErrorCode start() override; void stop() override; +#ifdef Q_OS_MAC + ErrorCode startMzImpl(); + ErrorCode stopMzImpl(); +#endif + private: QString configPath() const; void writeWireguardConfiguration(const QJsonObject &configuration); @@ -40,6 +47,9 @@ private: bool m_isConfigLoaded = false; +#ifdef Q_OS_MAC + QScopedPointer m_impl; +#endif }; #endif // WIREGUARDPROTOCOL_H diff --git a/client/ui/pages_logic/VpnLogic.cpp b/client/ui/pages_logic/VpnLogic.cpp index 0ba77e0f..a75aa1b9 100644 --- a/client/ui/pages_logic/VpnLogic.cpp +++ b/client/ui/pages_logic/VpnLogic.cpp @@ -148,7 +148,7 @@ void VpnLogic::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) break; case VpnProtocol::Connecting: pbConnectChecked = true; - pbConnectEnabled = false; + pbConnectEnabled = true; pbConnectVisible = false; rbModeEnabled = false; break; diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/systemtray_notificationhandler.cpp index c8d8eb35..381e2da0 100644 --- a/client/ui/systemtray_notificationhandler.cpp +++ b/client/ui/systemtray_notificationhandler.cpp @@ -6,7 +6,7 @@ #include "systemtray_notificationhandler.h" -#ifdef Q_OS_MACOS +#ifdef Q_OS_MAC # include "platforms/macos/macosutils.h" #endif diff --git a/client/utilities.cpp b/client/utilities.cpp index 41fde2f6..d286bcc7 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -231,8 +231,10 @@ QString Utils::wireguardExecPath() return Utils::executable("wireguard/wireguard-service", true); #elif defined Q_OS_LINUX return Utils::usrExecutable("wg-quick"); -#else +#elif defined Q_OS_MAC return Utils::executable("/wireguard", true); +#else + return {}; #endif } diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index f63834d1..8d98134d 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -12,28 +12,97 @@ qt_standard_project_setup() configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) set(HEADERS - ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.h - ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h - ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h - ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.h - ${CMAKE_CURRENT_LIST_DIR}/localserver.h - ${CMAKE_CURRENT_LIST_DIR}/logger.h - ${CMAKE_CURRENT_LIST_DIR}/router.h - ${CMAKE_CURRENT_LIST_DIR}/systemservice.h - ${CMAKE_CURRENT_BINARY_DIR}/version.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.h + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.h + ${CMAKE_CURRENT_LIST_DIR}/localserver.h + ${CMAKE_CURRENT_LIST_DIR}/logger.h + ${CMAKE_CURRENT_LIST_DIR}/router.h + ${CMAKE_CURRENT_LIST_DIR}/systemservice.h + ${CMAKE_CURRENT_BINARY_DIR}/version.h ) set(SOURCES - ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.cpp - ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp - ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp - ${CMAKE_CURRENT_LIST_DIR}/localserver.cpp - ${CMAKE_CURRENT_LIST_DIR}/logger.cpp - ${CMAKE_CURRENT_LIST_DIR}/main.cpp - ${CMAKE_CURRENT_LIST_DIR}/router.cpp - ${CMAKE_CURRENT_LIST_DIR}/systemservice.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp + ${CMAKE_CURRENT_LIST_DIR}/localserver.cpp + ${CMAKE_CURRENT_LIST_DIR}/logger.cpp + ${CMAKE_CURRENT_LIST_DIR}/main.cpp + ${CMAKE_CURRENT_LIST_DIR}/router.cpp + ${CMAKE_CURRENT_LIST_DIR}/systemservice.cpp ) +# Mozilla headres +set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/dnsutils.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/iputils.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/interfaceconfig.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/wireguardutils.h + + ${CMAKE_CURRENT_LIST_DIR}/../../client/platforms/dummy/dummynetworkwatcher.h + + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/ipaddress.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/loglevel.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/leakdetector.h + + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/models/server.h + + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/controllerimpl.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/dnspingsender.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/localsocketcontroller.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/networkwatcher.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/networkwatcherimpl.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/pinghelper.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/pingsender.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/pingsenderfactory.h +) + +include_directories(../../client/mozilla) +include_directories(../../client/mozilla/shared) +include_directories(../../client/mozilla/models) +include_directories(../../client/platforms/) + +# Mozilla sources +set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/models/server.cpp + + ${CMAKE_CURRENT_LIST_DIR}/../../client/platforms/dummy/dummynetworkwatcher.cpp + + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/ipaddress.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/leakdetector.cpp + + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/dnspingsender.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/localsocketcontroller.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/networkwatcher.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/pinghelper.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/pingsender.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/pingsenderfactory.cpp +) + +if(UNIX) + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/signalhandler.h + ) + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/../../client/mozilla/shared/signalhandler.cpp + ) +endif() + +if (WIN32 OR APPLE) + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemon.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonlocalserver.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonlocalserverconnection.h + ) + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemon.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonlocalserver.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonlocalserverconnection.cpp + ) +endif() + if(WIN32) set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.h @@ -70,6 +139,33 @@ if(APPLE) ${CMAKE_CURRENT_LIST_DIR}/helper_route_mac.c ${CMAKE_CURRENT_LIST_DIR}/router_mac.cpp ) + + #Mozilla + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/ios/iosnetworkwatcher.h + + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/macosnetworkwatcher.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/macospingsender.h + + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/dnsutilsmacos.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/iputilsmacos.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosdaemon.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosroutemonitor.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/ios/iosnetworkwatcher.mm + + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/macosnetworkwatcher.mm + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/macospingsender.cpp + + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/dnsutilsmacos.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/iputilsmacos.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosdaemon.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosroutemonitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.cpp + ) endif() if(LINUX) @@ -93,12 +189,37 @@ include_directories( add_executable(${PROJECT} ${SOURCES} ${HEADERS}) target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat ${LIBS}) +target_compile_definitions(${PROJECT} PRIVATE "MZ_$") + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG") +endif() if(APPLE) - set_target_properties(${PROJECT} PROPERTIES - INSTALL_RPATH "@executable_path/../Frameworks" - BUILD_WITH_INSTALL_RPATH TRUE - ) + if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + set_target_properties(${PROJECT} PROPERTIES + INSTALL_RPATH "@executable_path/../Frameworks" + BUILD_WITH_INSTALL_RPATH TRUE + ) + endif() + + find_library(FW_COREFOUNDATION CoreFoundation) + find_library(FW_SYSTEMCONFIG SystemConfiguration) + find_library(FW_SERVICEMGMT ServiceManagement) + find_library(FW_SECURITY Security) + find_library(FW_COREWLAN CoreWLAN) + find_library(FW_NETWORK Network) + find_library(FW_USER_NOTIFICATIONS UserNotifications) + + target_link_libraries(${PROJECT} PRIVATE ${FW_COREFOUNDATION}) + target_link_libraries(${PROJECT} PRIVATE ${FW_SYSTEMCONFIG}) + target_link_libraries(${PROJECT} PRIVATE ${FW_SERVICEMGMT}) + target_link_libraries(${PROJECT} PRIVATE ${FW_SECURITY}) + target_link_libraries(${PROJECT} PRIVATE ${FW_COREWLAN}) + target_link_libraries(${PROJECT} PRIVATE ${FW_NETWORK}) + target_link_libraries(${PROJECT} PRIVATE ${FW_USER_NOTIFICATIONS}) + + endif() qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_interface.rep) diff --git a/service/server/localserver.cpp b/service/server/localserver.cpp index 9971ad43..4b375d26 100644 --- a/service/server/localserver.cpp +++ b/service/server/localserver.cpp @@ -8,11 +8,16 @@ #include "utilities.h" #include "router.h" +#include "logger.h" #ifdef Q_OS_WIN #include "tapcontroller_win.h" #endif +namespace { +Logger logger("MacOSDaemonServer"); +} + LocalServer::LocalServer(QObject *parent) : QObject(parent), m_ipcServer(this) { @@ -34,6 +39,18 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent), m_serverNode.enableRemoting(&m_ipcServer); } }); + + // Init Mozilla Wireguard Daemon +#ifdef Q_OS_MAC + if (!server.initialize()) { + logger.error() << "Failed to initialize the server"; + return; + } + + // Signal handling for a proper shutdown. + QObject::connect(qApp, &QCoreApplication::aboutToQuit, + []() { MacOSDaemon::instance()->deactivate(); }); +#endif } LocalServer::~LocalServer() diff --git a/service/server/localserver.h b/service/server/localserver.h index bbd9b630..e4217cc0 100644 --- a/service/server/localserver.h +++ b/service/server/localserver.h @@ -10,6 +10,11 @@ #include "ipcserver.h" +#ifdef Q_OS_MAC +#include "macos/daemon/macosdaemon.h" +#include "../../client/daemon/daemonlocalserver.h" +#endif + class QLocalServer; class QLocalSocket; class QProcess; @@ -27,6 +32,11 @@ public: IpcServer m_ipcServer; QRemoteObjectHost m_serverNode; bool m_isRemotingEnabled = false; + +#ifdef Q_OS_MAC + MacOSDaemon daemon; + DaemonLocalServer server{qApp}; +#endif }; #endif // LOCALSERVER_H diff --git a/service/server/logger.cpp b/service/server/logger.cpp index f9d2ab4c..ab658796 100644 --- a/service/server/logger.cpp +++ b/service/server/logger.cpp @@ -1,6 +1,8 @@ #include "logger.h" #include +#include +#include #include #include @@ -103,3 +105,81 @@ void Logger::cleanUp() dir.removeRecursively(); #endif } + + +Logger::Log::Log(Logger* logger, LogLevel logLevel) + : m_logger(logger), m_logLevel(logLevel), m_data(new Data()) {} + +Logger::Log::~Log() { + qDebug() << "Amnezia" << m_logger->className() << m_data->m_buffer.trimmed(); + delete m_data; +} + +Logger::Log Logger::error() { return Log(this, LogLevel::Error); } +Logger::Log Logger::warning() { return Log(this, LogLevel::Warning); } +Logger::Log Logger::info() { return Log(this, LogLevel::Info); } +Logger::Log Logger::debug() { return Log(this, LogLevel::Debug); } +QString Logger::sensitive(const QString& input) { +#ifdef Q_DEBUG + return input; +#else + Q_UNUSED(input); + return QString(8, 'X'); +#endif +} + + +#define CREATE_LOG_OP_REF(x) \ +Logger::Log& Logger::Log::operator<<(x t) { \ + m_data->m_ts << t << ' '; \ + return *this; \ +} + +CREATE_LOG_OP_REF(uint64_t); +CREATE_LOG_OP_REF(const char*); +CREATE_LOG_OP_REF(const QString&); +CREATE_LOG_OP_REF(const QByteArray&); +CREATE_LOG_OP_REF(const void*); + +#undef CREATE_LOG_OP_REF + +Logger::Log& Logger::Log::operator<<(const QStringList& t) { + m_data->m_ts << '[' << t.join(",") << ']' << ' '; + return *this; +} + +Logger::Log& Logger::Log::operator<<(const QJsonObject& t) { + m_data->m_ts << QJsonDocument(t).toJson(QJsonDocument::Indented) << ' '; + return *this; +} + +Logger::Log& Logger::Log::operator<<(QTextStreamFunction t) { + m_data->m_ts << t; + return *this; +} + +void Logger::Log::addMetaEnum(quint64 value, const QMetaObject* meta, + const char* name) { + QMetaEnum me = meta->enumerator(meta->indexOfEnumerator(name)); + + QString out; + QTextStream ts(&out); + + if (const char* scope = me.scope()) { + ts << scope << "::"; + } + + const char* key = me.valueToKey(static_cast(value)); + const bool scoped = me.isScoped(); + if (scoped || !key) { + ts << me.enumName() << (!key ? "(" : "::"); + } + + if (key) { + ts << key; + } else { + ts << value << ")"; + } + + m_data->m_ts << out; +} diff --git a/service/server/logger.h b/service/server/logger.h index 59044470..bb920931 100644 --- a/service/server/logger.h +++ b/service/server/logger.h @@ -6,6 +6,8 @@ #include #include +#include "mozilla/shared/loglevel.h" + class Logger { public: @@ -17,12 +19,65 @@ public: static void clearLogs(); static void cleanUp(); + // compat with Mozilla logger + Logger(const QString &className) { m_className = className; } + const QString& className() const { return m_className; } + + class Log { + public: + Log(Logger* logger, LogLevel level); + ~Log(); + + Log& operator<<(uint64_t t); + Log& operator<<(const char* t); + Log& operator<<(const QString& t); + Log& operator<<(const QStringList& t); + Log& operator<<(const QByteArray& t); + Log& operator<<(const QJsonObject& t); + Log& operator<<(QTextStreamFunction t); + Log& operator<<(const void* t); + + // Q_ENUM + template + typename std::enable_if::Value, Log&>::type + operator<<(T t) { + const QMetaObject* meta = qt_getEnumMetaObject(t); + const char* name = qt_getEnumName(t); + addMetaEnum(typename QFlags::Int(t), meta, name); + return *this; + } + + private: + void addMetaEnum(quint64 value, const QMetaObject* meta, const char* name); + + Logger* m_logger; + LogLevel m_logLevel; + + struct Data { + Data() : m_ts(&m_buffer, QIODevice::WriteOnly) {} + + QString m_buffer; + QTextStream m_ts; + }; + + Data* m_data; + }; + + Log error(); + Log warning(); + Log info(); + Log debug(); + QString sensitive(const QString& input); + private: friend void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg); static QFile m_file; static QString m_logFileName; static QTextStream m_textStream; + + // compat with Mozilla logger + QString m_className; }; #endif // LOGGER_H