Merge to dev branch

This commit is contained in:
Anh TV 2024-10-04 21:52:50 +07:00 committed by Yaroslav Yashin
parent 7261029082
commit 5ab4318c23
38 changed files with 3554 additions and 49 deletions

View file

@ -31,13 +31,14 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE AND NOT IOS) if((APPLE AND NOT IOS) OR (DEFINED MACOS_NE AND MACOS_NE AND NOT IOS))
set(CMAKE_OSX_ARCHITECTURES "x86_64") set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif() endif()
add_subdirectory(client) add_subdirectory(client)
if(NOT IOS AND NOT ANDROID) # Mac OSX with Network Extension don't need service
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(service) add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake) include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)

View file

@ -25,13 +25,13 @@ execute_process(
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}") add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}") add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}") add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}") add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) #Macos Network Extension doesn't need Widgets
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID) OR (NOT MACOS_NE))
set(PACKAGES ${PACKAGES} Widgets) set(PACKAGES ${PACKAGES} Widgets)
endif() endif()
@ -44,14 +44,15 @@ set(LIBS ${LIBS}
Qt6::Core5Compat Qt6::Concurrent Qt6::Core5Compat Qt6::Concurrent
) )
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) #Macos Network Extension doesn't need Widgets
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID) OR (APPLE NOT MACOS_NE))
set(LIBS ${LIBS} Qt6::Widgets) set(LIBS ${LIBS} Qt6::Widgets)
endif() endif()
qt_standard_project_setup() qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION) qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID) OR (APPLE NOT MACOS_NE))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
@ -88,6 +89,17 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
# -- i18n end # -- i18n end
if(IOS)
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
endif()
# Build openvpn adapter for MacOS Network Extension
if(MACOS_NE)
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/macos/scripts/openvpn.sh args
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
endif()
set(IS_CI ${CI}) set(IS_CI ${CI})
if(IS_CI) if(IS_CI)
message("Detected CI env") message("Detected CI env")
@ -133,7 +145,6 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h ${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h ${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.h
) )
# Mozilla headres # Mozilla headres
@ -149,7 +160,7 @@ include_directories(mozilla)
include_directories(mozilla/shared) include_directories(mozilla/shared)
include_directories(mozilla/models) include_directories(mozilla/models)
if(NOT IOS) if((NOT IOS) OR (NOT MACOS_NE))
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
) )
@ -185,7 +196,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.cpp
) )
# Mozilla sources # Mozilla sources
@ -200,7 +210,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG") target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
endif() endif()
if(NOT IOS) if((NOT IOS) OR (NOT MACOS_NE))
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
) )
@ -308,7 +318,8 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux) link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif() endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) # Macos Network Extension doesn't need
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID) OR (APPLE NOT MACOS_NE))
message("Client desktop build") message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP) add_compile_definitions(AMNEZIA_DESKTOP)
@ -344,9 +355,11 @@ endif()
if(IOS) if(IOS)
include(cmake/ios.cmake) include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake) include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND NOT IOS) elseif(APPLE AND NOT IOS AND NOT MACOS_NE)
include(cmake/osxtools.cmake) include(cmake/osxtools.cmake)
include(cmake/macos.cmake) include(cmake/macos.cmake)
elseif(APPLE AND NOT IOS AND MACOS_NE)
include(cmake/macos_ne.cmake)
endif() endif()
target_link_libraries(${PROJECT} PRIVATE ${LIBS}) target_link_libraries(${PROJECT} PRIVATE ${LIBS})

181
client/cmake/macos_ne.cmake Normal file
View file

@ -0,0 +1,181 @@
message("Client ==> iOS 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(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)
find_library(FW_NETWORKEXTENSION NetworkExtension)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
# ${FW_UIKIT}
${FW_AVFOUNDATION}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
# set(HEADERS ${HEADERS}
# ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h
# )
# set(SOURCES ${SOURCES}
# ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
# )
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_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 "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
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_STYLE Automatic
#XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution: Privacy Technologies OU (X7UJ388FXK)"
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
#XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "Mac AppStore AmneziaVPN"
#XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "Mac AppStore AmneziaVPN"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
)
target_sources(${PROJECT} PRIVATE
#${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
#${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(macos/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")
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
# add_custom_command(TARGET ${PROJECT} POST_BUILD
# COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
# )
# if(CMAKE_BUILD_TYPE STREQUAL "Release")
# SET(SIGN_CMD codesign --deep --force --sign 'Apple Distribution: Privacy Technologies OU \(X7UJ388FXK\)' --timestamp --options runtime $<TARGET_BUNDLE_DIR:AmneziaVPN>)
# message("Manual signing bundle...")
# message(${SIGN_CMD})
# add_custom_command(TARGET ${PROJECT} POST_BUILD
# COMMAND ${SIGN_CMD}
# )
# endif()

View file

@ -3,40 +3,26 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.application-identifier</key> <key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string> <string>X7UJ388FXK.org.amnezia.AmneziaVPN.network-extension</string>
<key>com.apple.developer.networking.networkextension</key> <key>com.apple.developer.networking.networkextension</key>
<array> <array>
<string>packet-tunnel-provider</string> <string>packet-tunnel-provider</string>
</array> </array>
<key>keychain-access-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
</array>
<key>com.apple.developer.team-identifier</key> <key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string> <string>X7UJ388FXK</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string> <string>group.org.amnezia.AmneziaVPN</string>
</array> </array>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>com.apple.security.app-sandbox</key> <key>keychain-access-groups</key>
<true/> <array>
<key>com.apple.private.network.socket-delegate</key> <string>$(AppIdentifierPrefix)org.amnezia.AmneziaVPN.network-extension</string>
<true/> </array>
</dict> </dict>
</plist> </plist>

View file

@ -0,0 +1,198 @@
enable_language(Swift)
message("Client message >> macos build >> networkextension")
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
add_executable(networkextension)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/Info.plist.in
${CMAKE_CURRENT_BINARY_DIR}/Info.plist
)
set_target_properties(networkextension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
BUNDLE_EXTENSION appex
#MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/Info.plist
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
# XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
# #XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
# #XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution: Privacy Technologies OU (X7UJ388FXK)"
# #XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
# #XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "Mac AppStore AmneziaVPN.network-extension"
# #XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "Mac AppStore AmneziaVPN.network-extension"
XCODE_ATTRIBUTE_INFOPLIST_FILE "${CMAKE_CURRENT_BINARY_DIR}/Info.plist"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
#XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution: Privacy Technologies OU (X7UJ388FXK)"
#XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
#XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "Mac AppStore AmneziaVPN.network-extension"
#XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "Mac AppStore AmneziaVPN.network-extension"
)
endif()
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
)
set_target_properties("networkextension" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
find_library(FW_MOBILE_CORE MobileCoreServices)
find_library(FW_UI_KIT UIKit)
find_library(FW_LIBRESOLV libresolv.9.tbd)
# set(OpenVPNAdapter_DIR "${CLIENT_ROOT_DIR}/3rd/")
# find_library(OPENVPN_ADAPTER_LIBRARY OpenVPNAdapter PATHS ${OpenVPNAdapter_DIR})
# target_link_libraries(networkextension PRIVATE ${OPENVPN_ADAPTER_LIBRARY})
# add_custom_command(TARGET networkextension PRE_BUILD
# COMMAND ${CMAKE_COMMAND} -E make_directory $<TARGET_FILE_DIR:networkextension>/../Frameworks
# )
# add_custom_command(TARGET networkextension POST_BUILD
# COMMAND ${CMAKE_COMMAND} -E echo "Copying ${OPENVPN_ADAPTER_LIBRARY} to $<TARGET_FILE_DIR:networkextension>/../Frameworks/"
# COMMAND ${CMAKE_COMMAND} -E copy_if_different
# ${OPENVPN_ADAPTER_LIBRARY}
# $<TARGET_FILE_DIR:networkextension>/../Frameworks/
# COMMAND ${CMAKE_COMMAND} -E echo "Copy complete"
# )
# Set the root directory
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
# Embedding the framework using CLIENT_ROOT_DIR
set_property(TARGET networkextension PROPERTY XCODE_EMBED_FRAMEWORKS
"${CLIENT_ROOT_DIR}/3rd/OpenVPNAdapter/build/Release-macos/OpenVPNAdapter.framework"
)
# Setting the framework search paths using CLIENT_ROOT_DIR
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "${CLIENT_ROOT_DIR}/3rd/OpenVPNAdapter/build/Release-macos")
# Linking the framework using CLIENT_ROOT_DIR
target_link_libraries("networkextension" PRIVATE "${CLIENT_ROOT_DIR}/3rd/OpenVPNAdapter/build/Release-macos/OpenVPNAdapter.framework")
#target_link_libraries(networkextension PRIVATE ${FW_ASSETS_LIBRARY})
#target_link_libraries(networkextension PRIVATE ${FW_MOBILE_CORE})
#target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV})
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/Log.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/macos_ne/iosglue.mm
)
target_sources(networkextension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
set_property(TARGET networkextension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
## Build wireguard-go-version.h
execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
OUTPUT_VARIABLE WG_VERSION_FULL
)
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_sources(networkextension PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/ios/arm64/libwg-go.a)
# Print the root directory for debugging purposes
message("---------")
message(${CLIENT_ROOT_DIR})
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_link_libraries("networkextension" PRIVATE "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a")
target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)

View file

@ -3,27 +3,32 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>en</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>AmneziaVPNNetworkExtension</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> <string>${BUILD_IOS_APP_IDENTIFIER}.network-extension</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string> <string>AmneziaVPNNetworkExtension</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string> <string>${APPLE_PROJECT_VERSION}</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string> <string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string> <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<key>NSExtension</key> <key>NSExtension</key>
<dict> <dict>
<key>NSExtensionPointIdentifier</key> <key>NSExtensionPointIdentifier</key>
@ -31,5 +36,11 @@
<key>NSExtensionPrincipalClass</key> <key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string> <string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict> </dict>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>com.wireguard.macos.app_group_id</key>
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
</dict> </dict>
</plist> </plist>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -1,10 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macos/gobridge/wireguard.h"
#include "wireguard-go-version.h" #include "wireguard-go-version.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
@ -23,3 +19,8 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]); bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
void write_msg_to_log(const char* tag, const char* msg); void write_msg_to_log(const char* tag, const char* msg);
// Khai báo hàm C để Swift có thể sử dụng
void hev_socks5_tunnel_quit(void);
// Updated function definition in C
int hev_socks5_tunnel_main(const char* configFile, int fd);

View file

@ -0,0 +1,3 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION

View file

@ -0,0 +1,29 @@
XCODEBUILD="/usr/bin/xcodebuild"
WORKINGDIR=`pwd`
PATCH="/usr/bin/patch"
# Copy the Project.xcconfig settings to amnezia.xcconfig
cat $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/Project.xcconfig > $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig
# Append macOS-specific build directory configurations to amnezia.xcconfig
cat << EOF >> $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig
PROJECT_TEMP_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/OpenVPNAdapter.build
CONFIGURATION_BUILD_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-macos
BUILT_PRODUCTS_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-macos
EOF
# Fetch the current macOS SDK version dynamically
MACOSX_SDK=macosx15.0
cd 3rd/OpenVPNAdapter
# Build for macOS using the correct SDK and destination
if $XCODEBUILD -scheme OpenVPNAdapter -configuration Release -xcconfig Configuration/amnezia.xcconfig -sdk $MACOSX_SDK -destination 'generic/platform=macOS' -project OpenVPNAdapter.xcodeproj ; then
echo "OpenVPNAdapter built successfully for macOS"
else
echo "OpenVPNAdapter macOS build failed ..."
fi
# Remove CodeSignature if needed for macOS
rm -rf ./build/Release-macos/OpenVPNAdapter.framework/Versions/A/_CodeSignature
cd ../../

View file

@ -0,0 +1,74 @@
import Darwin
import SystemConfiguration
public enum Socks5Tunnel {
private static var tunnelFileDescriptor: Int32? {
var ctlInfo = ctl_info()
withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
_ = strcpy($0, "com.apple.net.utun_control") // strcpy comes from Darwin
}
}
for fd: Int32 in 0...1024 {
var addr = sockaddr_ctl()
var ret: Int32 = -1
var len = socklen_t(MemoryLayout.size(ofValue: addr)) // socklen_t comes from Darwin
withUnsafeMutablePointer(to: &addr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) { // sockaddr from Darwin
ret = getpeername(fd, $0, &len) // getpeername from Darwin
}
}
if ret != 0 || addr.sc_family != AF_SYSTEM { // AF_SYSTEM from Darwin
continue
}
if ctlInfo.ctl_id == 0 {
ret = ioctl(fd, CTLIOCGINFO, &ctlInfo) // ioctl from Darwin
if ret != 0 {
continue
}
}
if addr.sc_id == ctlInfo.ctl_id {
return fd
}
}
return nil
}
private static var interfaceName: String? {
guard let tunnelFileDescriptor = self.tunnelFileDescriptor else {
return nil
}
var buffer = [UInt8](repeating: 0, count: Int(IFNAMSIZ)) // IFNAMSIZ from Darwin
return buffer.withUnsafeMutableBufferPointer { mutableBufferPointer in
guard let baseAddress = mutableBufferPointer.baseAddress else {
return nil
}
var ifnameSize = socklen_t(IFNAMSIZ) // socklen_t and IFNAMSIZ from Darwin
let result = getsockopt(
tunnelFileDescriptor,
2 /* SYSPROTO_CONTROL */,
2 /* UTUN_OPT_IFNAME */,
baseAddress,
&ifnameSize
)
if result == 0 {
return String(cString: baseAddress)
} else {
return nil
}
}
}
@discardableResult
public static func run(withConfig filePath: String) -> Int32 {
guard let fileDescriptor = self.tunnelFileDescriptor else {
fatalError("Get tunnel file descriptor failed.")
}
return hev_socks5_tunnel_main(filePath.cString(using: .utf8), fileDescriptor)
}
public static func quit() {
hev_socks5_tunnel_quit()
}
}

View file

@ -0,0 +1,122 @@
import Foundation
import os.log
struct Log {
static let osLog = Logger()
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
static var isLoggingEnabled: Bool {
get {
sharedUserDefaults.bool(forKey: IsLoggingEnabledKey)
}
set {
sharedUserDefaults.setValue(newValue, forKey: IsLoggingEnabledKey)
}
}
private static let appGroupID = "group.org.amnezia.AmneziaVPN"
static let appLogURL = {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
return sharedContainerURL.appendingPathComponent("app.log", isDirectory: false)
}()
static let neLogURL = {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
return sharedContainerURL.appendingPathComponent("ne.log", isDirectory: false)
}()
private static var sharedUserDefaults = {
UserDefaults(suiteName: appGroupID)!
}()
static let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter
}()
var records = [Record]()
var lastRecordDate = Date.distantPast
init() {
self.records = []
}
init(_ str: String) {
records = str.split(whereSeparator: \.isNewline)
.map {
if let record = Record(String($0)) {
lastRecordDate = record.date
return record
} else {
return Record(date: lastRecordDate, level: .error, message: "LOG: \($0)")
}
}
}
init?(at url: URL) {
if !FileManager.default.fileExists(atPath: url.path) {
guard (try? "".data(using: .utf8)?.write(to: url)) != nil else { return nil }
}
guard let fileHandle = try? FileHandle(forUpdating: url) else { return nil }
defer { fileHandle.closeFile() }
guard
let data = try? fileHandle.readToEnd(),
let str = String(data: data, encoding: .utf8) else {
return nil
}
self.init(str)
}
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
NSLog("\(title) \(message)")
guard isLoggingEnabled else { return }
osLog.log(level: type, "\(title) \(message)")
let date = Date()
let level = Record.Level(from: type)
let messages = message.split(whereSeparator: \.isNewline)
for index in 0..<messages.count {
let message = String(messages[index])
if index != 0 && message.first != " " {
Record(date: date, level: level, message: "\(title) \(message)").save(at: url)
} else {
Record(date: date, level: level, message: "\(title)\(message)").save(at: url)
}
}
}
static func clear(at url: URL) {
if FileManager.default.fileExists(atPath: url.path) {
guard let fileHandle = try? FileHandle(forUpdating: url) else { return }
defer { fileHandle.closeFile() }
try? fileHandle.truncate(atOffset: 0)
}
}
}
extension Log: CustomStringConvertible {
var description: String {
records
.map {
$0.description
}
.joined(separator: "\n")
}
}
func log(_ type: OSLogType, title: String = "", message: String) {
Log.log(type, title: "App: \(title)", message: message, url: Log.appLogURL)
}

View file

@ -0,0 +1,33 @@
import Foundation
public func swiftUpdateLogData(_ qtString: std.string) -> std.string {
let qtLog = Log(String(describing: qtString))
var log = qtLog
if let appLog = Log(at: Log.appLogURL) {
appLog.records.forEach {
log.records.append($0)
}
}
if let neLog = Log(at: Log.neLogURL) {
neLog.records.forEach {
log.records.append($0)
}
}
log.records.sort {
$0.date < $1.date
}
return std.string(log.description)
}
public func swiftDeleteLog() {
Log.clear(at: Log.appLogURL)
Log.clear(at: Log.neLogURL)
}
public func toggleLogging(_ isEnabled: Bool) {
Log.isLoggingEnabled = isEnabled
}

View file

@ -0,0 +1,103 @@
import Foundation
import os.log
extension Log {
struct Record {
let date: Date
let level: Level
let message: String
init?(_ str: String) {
let dateStr = String(str.prefix(19))
guard let date = Log.dateFormatter.date(from: dateStr) else { return nil }
let str = str.dropFirst(20)
guard let endIndex = str.firstIndex(of: " ") else { return nil }
let levelStr = String(str[str.startIndex..<endIndex])
guard let level = Level(rawValue: levelStr) else { return nil }
let messageStartIndex = str.index(after: endIndex)
let message = String(str[messageStartIndex..<str.endIndex])
self.init(date: date, level: level, message: message)
}
init(date: Date, level: Level, message: String) {
self.date = date
self.level = level
self.message = message
}
func save(at url: URL) {
osLog.log(level: level.osLogType, "\(message)")
guard let data = "\n\(description)".data(using: .utf8) else { return }
if !FileManager.default.fileExists(atPath: url.path) {
guard (try? "".data(using: .utf8)?.write(to: url)) != nil else { return }
}
guard let fileHandle = try? FileHandle(forUpdating: url) else { return }
defer { fileHandle.closeFile() }
guard (try? fileHandle.seekToEnd()) != nil else { return }
try? fileHandle.write(contentsOf: data)
}
}
}
extension Log.Record: CustomStringConvertible {
var description: String {
"\(Log.dateFormatter.string(from: date)) \(level.rawValue) \(message)"
}
}
extension Log.Record {
enum Level: String {
case debug
case warning
case error
case critical
case fatal
case info
case system // critical
init(from osLogType: OSLogType) {
switch osLogType {
case .default:
self = .info
case .info:
self = .info
case .debug:
self = .debug
case .error:
self = .error
case .fault:
self = .fatal
default:
self = .info
}
}
var osLogType: OSLogType {
switch self {
case .info:
return .info
case .debug:
return .debug
case .error:
return .error
case .fatal:
return .fault
case .warning:
return .info
case .critical:
return .fault
case .system:
return .fault
}
}
}
}

View file

@ -0,0 +1,22 @@
import Foundation
import os.log
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
}
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
neLog(type, title: "WG: \(title)", message: message)
}
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
neLog(type, title: "OVPN: \(title)", message: message)
}
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
neLog(type, title: "XRAY: \(title)", message: message)
}
public func neLog(_ type: OSLogType, title: String = "", message: String) {
Log.log(type, title: "NE: \(title)", message: message)
}

View file

@ -0,0 +1,234 @@
import Foundation
import NetworkExtension
import OpenVPNAdapter
struct OpenVPNConfig: Decodable {
let config: String
let splitTunnelType: Int
let splitTunnelSites: [String]
var str: String {
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
}
}
extension PacketTunnelProvider {
func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
ovpnLog(.error, message: "Can't start")
return
}
do {
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
} catch {
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
}
return
}
}
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
withShadowSocks viaSS: Bool = false,
completionHandler: @escaping (Error?) -> Void) {
ovpnLog(.info, message: "Setup and launch")
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnConfiguration
if str.contains("cloak") {
configuration.setPTCloak()
}
let evaluation: OpenVPNConfigurationEvaluation?
do {
ovpnAdapter = OpenVPNAdapter()
ovpnAdapter?.delegate = self
evaluation = try ovpnAdapter?.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
if evaluation?.autologin == false {
ovpnLog(.info, message: "Implement login with user credentials")
}
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
ovpnAdapter?.connect(using: packetFlow)
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter?.transportStatistics.bytesIn
let bytesout = ovpnAdapter?.transportStatistics.bytesOut
guard let bytesin, let bytesout else {
completionHandler(nil)
return
}
let response: [String: Any] = [
"rx_bytes": bytesin,
"tx_bytes": bytesout
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
ovpnAdapter?.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
completionHandler: @escaping (Error?) -> Void
) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
if splitTunnelType == 1 {
var ipv4IncludedRoutes = [NEIPv4Route]()
guard let splitTunnelSites else {
completionHandler(NSError(domain: "Split tunnel sited not setted up", code: 0))
return
}
for allowedIPString in splitTunnelSites {
if let allowedIP = IPAddressRange(from: allowedIPString) {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allowedIP.address)",
subnetMask: "\(allowedIP.subnetMask())"))
}
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
} else {
if splitTunnelType == 2 {
var ipv4ExcludedRoutes = [NEIPv4Route]()
var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]()
guard let splitTunnelSites else {
completionHandler(NSError(domain: "Split tunnel sited not setted up", code: 0))
return
}
for excludeIPString in splitTunnelSites {
if let excludeIP = IPAddressRange(from: excludeIPString) {
ipv4ExcludedRoutes.append(NEIPv4Route(
destinationAddress: "\(excludeIP.address)",
subnetMask: "\(excludeIP.subnetMask())"))
}
}
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allIPv4.address)",
subnetMask: "\(allIPv4.subnetMask())"))
}
if let allIPv6 = IPAddressRange(from: "::/0") {
ipv6IncludedRoutes.append(NEIPv6Route(
destinationAddress: "\(allIPv6.address)",
networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
}
}
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
handleEvent event: OpenVPNAdapterEvent,
message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
// Handle log messages
ovpnLog(.info, message: logMessage)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}

View file

@ -0,0 +1,187 @@
import Foundation
import NetworkExtension
extension PacketTunnelProvider {
func startWireguard(activationAttemptId: String?,
errorNotifier: ErrorNotifier,
completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
wg_log(.error, message: "Can't start, config missing")
completionHandler(nil)
return
}
do {
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
let wgConfigStr = wgConfig.str
wg_log(.info, title: "config: ", message: wgConfig.redux)
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
if tunnelConfiguration.peers.first!.allowedIPs
.map({ $0.stringRepresentation })
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
if wgConfig.splitTunnelType == 1 {
for index in tunnelConfiguration.peers.indices {
tunnelConfiguration.peers[index].allowedIPs.removeAll()
var allowedIPs = [IPAddressRange]()
for allowedIPString in wgConfig.splitTunnelSites {
if let allowedIP = IPAddressRange(from: allowedIPString) {
allowedIPs.append(allowedIP)
}
}
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
}
} else if wgConfig.splitTunnelType == 2 {
for index in tunnelConfiguration.peers.indices {
var excludeIPs = [IPAddressRange]()
for excludeIPString in wgConfig.splitTunnelSites {
if let excludeIP = IPAddressRange(from: excludeIPString) {
excludeIPs.append(excludeIP)
}
}
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
}
}
}
wg_log(.info, message: "Starting tunnel from the " +
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
// Start the tunnel
wgAdapter = WireGuardAdapter(with: self) { logLevel, message in
wg_log(logLevel.osLogLevel, message: message)
}
wgAdapter?.start(tunnelConfiguration: tunnelConfiguration) { [weak self] adapterError in
guard let adapterError else {
let interfaceName = self?.wgAdapter?.interfaceName ?? "unknown"
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
completionHandler(nil)
return
}
switch adapterError {
case .cannotLocateTunnelFileDescriptor:
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
case .dnsResolution(let dnsErrors):
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
.joined(separator: ", ")
wg_log(.error, message:
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
case .setNetworkSettings(let error):
wg_log(.error, message:
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
case .startWireGuardBackend(let errorCode):
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
case .invalidState:
fatalError()
}
}
} catch {
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
completionHandler(nil)
return
}
}
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
wgAdapter?.getRuntimeConfiguration { settings in
let components = settings!.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:]
for component in components {
let pair = component.components(separatedBy: "=")
if pair.count == 2 {
settingsDictionary[pair[0]] = pair[1]
}
}
let response: [String: Any] = [
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
}
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 {
wgAdapter?.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
} else if messageData.count >= 1 {
// Updates the tunnel configuration and responds with the active configuration
wg_log(.info, message: "Switching tunnel configuration")
guard let configString = String(data: messageData, encoding: .utf8)
else {
completionHandler(nil)
return
}
do {
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
wgAdapter?.update(tunnelConfiguration: tunnelConfiguration) { [weak self] error in
if let error {
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
completionHandler(nil)
return
}
self?.wgAdapter?.getRuntimeConfiguration { settings in
var data: Data?
if let settings {
data = settings.data(using: .utf8)!
}
completionHandler(data)
}
}
} catch {
completionHandler(nil)
}
} else {
completionHandler(nil)
}
}
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile()
if let error {
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
}
completionHandler()
#if os(macOS)
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
// Remove it when they finally fix this upstream and the fix has been rolled out to
// sufficient quantities of users.
exit(0)
#endif
}
}
}

View file

@ -0,0 +1,166 @@
import Foundation
import NetworkExtension
import WireGuardKitGo
enum XrayErrors: Error {
case noXrayConfig
case cantSaveXrayConfig
case cantParseListenAndPort
case cantSaveHevSocksConfig
}
extension Constants {
static let cachesDirectory: URL = {
if let cachesDirectoryURL = FileManager.default.urls(for: .cachesDirectory,
in: .userDomainMask).first {
return cachesDirectoryURL
} else {
fatalError("Unable to retrieve caches directory.")
}
}()
}
extension PacketTunnelProvider {
func startXray(completionHandler: @escaping (Error?) -> Void) {
// Xray configuration
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let xrayConfigData = providerConfiguration[Constants.xrayConfigKey] as? Data else {
xrayLog(.error, message: "Can't get xray configuration")
completionHandler(XrayErrors.noXrayConfig)
return
}
// Tunnel settings
let ipv6Enabled = true
let hideVPNIcon = false
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "254.1.1.1")
settings.mtu = 9000
settings.ipv4Settings = {
let settings = NEIPv4Settings(addresses: ["198.18.0.1"], subnetMasks: ["255.255.0.0"])
settings.includedRoutes = [NEIPv4Route.default()]
return settings
}()
settings.ipv6Settings = {
guard ipv6Enabled else {
return nil
}
let settings = NEIPv6Settings(addresses: ["fd6e:a81b:704f:1211::1"], networkPrefixLengths: [64])
settings.includedRoutes = [NEIPv6Route.default()]
if hideVPNIcon {
settings.excludedRoutes = [NEIPv6Route(destinationAddress: "::", networkPrefixLength: 128)]
}
return settings
}()
let dns = ["8.8.4.4","1.1.1.1"]
settings.dnsSettings = NEDNSSettings(servers: dns)
do {
let port = 10808
let address = "::1"
let jsonDict = try JSONSerialization.jsonObject(with: xrayConfigData,
options: []) as? [String: Any]
guard var jsonDict else {
xrayLog(.error, message: "Can't parse address and port for hevSocks")
completionHandler(XrayErrors.cantParseListenAndPort)
return
}
if var inboundsArray = jsonDict["inbounds"] as? [[String: Any]], !inboundsArray.isEmpty {
inboundsArray[0]["port"] = port
inboundsArray[0]["listen"] = address
jsonDict["inbounds"] = inboundsArray
}
let updatedData = try JSONSerialization.data(withJSONObject: jsonDict, options: [])
setTunnelNetworkSettings(settings) { [weak self] error in
if let error {
completionHandler(error)
return
}
// Launch xray
self?.setupAndStartXray(configData: updatedData) { xrayError in
if let xrayError {
completionHandler(xrayError)
return
}
// Launch hevSocks
self?.setupAndRunTun2socks(configData: updatedData,
address: address,
port: port,
completionHandler: completionHandler)
}
}
} catch {
completionHandler(error)
return
}
}
func stopXray(completionHandler: () -> Void) {
Socks5Tunnel.quit()
LibXrayStopXray()
completionHandler()
}
private func setupAndStartXray(configData: Data,
completionHandler: @escaping (Error?) -> Void) {
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
guard FileManager.default.createFile(atPath: path, contents: configData) else {
xrayLog(.error, message: "Can't save xray configuration")
completionHandler(XrayErrors.cantSaveXrayConfig)
return
}
LibXrayRunXray(nil,
path,
Int64.max)
completionHandler(nil)
xrayLog(.info, message: "Xray started")
}
private func setupAndRunTun2socks(configData: Data,
address: String,
port: Int,
completionHandler: @escaping (Error?) -> Void) {
let config = """
tunnel:
mtu: 9000
socks5:
port: \(port)
address: \(address)
udp: 'udp'
misc:
task-stack-size: 20480
connect-timeout: 5000
read-write-timeout: 60000
log-file: stderr
log-level: error
limit-nofile: 65535
"""
let configurationFilePath = Constants.cachesDirectory.appendingPathComponent("config.yml", isDirectory: false).path
guard FileManager.default.createFile(atPath: configurationFilePath, contents: config.data(using: .utf8)!) else {
xrayLog(.info, message: "Cant save hevSocks configuration")
completionHandler(XrayErrors.cantSaveHevSocksConfig)
return
}
DispatchQueue.global().async {
xrayLog(.info, message: "Hev socks started")
completionHandler(nil)
Socks5Tunnel.run(withConfig: configurationFilePath)
}
}
}

View file

@ -0,0 +1,234 @@
import Foundation
import NetworkExtension
import os
import Darwin
import OpenVPNAdapter
enum TunnelProtoType: String {
case wireguard, openvpn, xray
}
struct Constants {
static let kDefaultPathKey = "defaultPath"
static let processQueueName = "org.amnezia.process-packets"
static let kActivationAttemptId = "activationAttemptId"
static let ovpnConfigKey = "ovpn"
static let xrayConfigKey = "xray"
static let wireGuardConfigKey = "wireguard"
static let loggerTag = "NET"
static let kActionStart = "start"
static let kActionRestart = "restart"
static let kActionStop = "stop"
static let kActionGetTunnelId = "getTunnelId"
static let kActionStatus = "status"
static let kActionIsServerReachable = "isServerReachable"
static let kMessageKeyAction = "action"
static let kMessageKeyTunnelId = "tunnelId"
static let kMessageKeyConfig = "config"
static let kMessageKeyErrorCode = "errorCode"
static let kMessageKeyHost = "host"
static let kMessageKeyPort = "port"
static let kMessageKeyOnDemand = "is-on-demand"
static let kMessageKeySplitTunnelType = "SplitTunnelType"
static let kMessageKeySplitTunnelSites = "SplitTunnelSites"
}
class PacketTunnelProvider: NEPacketTunnelProvider {
var wgAdapter: WireGuardAdapter?
var ovpnAdapter: OpenVPNAdapter?
var splitTunnelType: Int?
var splitTunnelSites: [String]?
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var protoType: TunnelProtoType?
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let message = String(data: messageData, encoding: .utf8) else {
if let completionHandler {
completionHandler(nil)
}
return
}
neLog(.info, title: "App said: ", message: message)
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
neLog(.error, message: "Failed to serialize message from app")
return
}
guard let completionHandler else {
neLog(.error, message: "Missing message completion handler")
return
}
guard let action = message[Constants.kMessageKeyAction] as? String else {
neLog(.error, message: "Missing action key in app message")
completionHandler(nil)
return
}
if action == Constants.kActionStatus {
handleStatusAppMessage(messageData,
completionHandler: completionHandler)
}
}
override func startTunnel(options: [String : NSObject]? = nil,
completionHandler: @escaping ((any Error)?) -> Void) {
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
neLog(.info, message: "Start tunnel")
if let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol {
let providerConfiguration = protocolConfiguration.providerConfiguration
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
protoType = .openvpn
} else if (providerConfiguration?[Constants.wireGuardConfigKey] as? Data) != nil {
protoType = .wireguard
} else if (providerConfiguration?[Constants.xrayConfigKey] as? Data) != nil {
protoType = .xray
}
}
guard let protoType else {
let error = NSError(domain: "Protocol is not selected", code: 0)
completionHandler(error)
return
}
switch protoType {
case .wireguard:
startWireguard(activationAttemptId: activationAttemptId,
errorNotifier: errorNotifier,
completionHandler: completionHandler)
case .openvpn:
startOpenVPN(completionHandler: completionHandler)
case .xray:
startXray(completionHandler: completionHandler)
}
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
guard let protoType else {
completionHandler()
return
}
switch protoType {
case .wireguard:
stopWireguard(with: reason,
completionHandler: completionHandler)
case .openvpn:
stopOpenVPN(with: reason,
completionHandler: completionHandler)
case .xray:
stopXray(completionHandler: completionHandler)
}
}
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let protoType else {
completionHandler?(nil)
return
}
switch protoType {
case .wireguard:
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
case .openvpn:
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
case .xray:
break;
}
}
// MARK: Network observing methods
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey != keyPath else { return }
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
// comparing the paths' string description, which includes properties not exposed by the class
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
let defPath = defaultPath,
lastPath != defPath || lastPath.description != defPath.description else {
return
}
DispatchQueue.main.async { [weak self] in
guard let self, self.defaultPath != nil else { return }
self.handle(networkChange: self.defaultPath!) { _ in }
}
}
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
}
}
extension WireGuardLogLevel {
var osLogLevel: OSLogType {
switch self {
case .verbose:
return .debug
case .error:
return .error
}
}
}
extension NEProviderStopReason: CustomStringConvertible {
public var description: String {
switch self {
case .none:
return "No specific reason"
case .userInitiated:
return "The user stopped the NE"
case .providerFailed:
return "The NE failed to function correctly"
case .noNetworkAvailable:
return "No network connectivity is currently available"
case .unrecoverableNetworkChange:
return "The devices network connectivity changed"
case .providerDisabled:
return "The NE was disabled"
case .authenticationCanceled:
return "The authentication process was canceled"
case .configurationFailed:
return "The VPNC is invalid"
case .idleTimeout:
return "The session timed out"
case .configurationDisabled:
return "The VPNC was disabled"
case .configurationRemoved:
return "The VPNC was removed"
case .superceded:
return "VPNC was superceded by a higher-priority VPNC"
case .userLogout:
return "The user logged out"
case .userSwitch:
return "The current console user changed"
case .connectionFailed:
return "The connection failed"
case .sleep:
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
case .appUpdate:
return "appUpdat"
@unknown default:
return "@unknown default"
}
}
}

View file

@ -0,0 +1,14 @@
#include "QRCodeReaderBase.h"
QRCodeReader::QRCodeReader()
{
}
QRect QRCodeReader::cameraSize() {
return QRect();
}
void QRCodeReader::startReading() {}
void QRCodeReader::stopReading() {}
void QRCodeReader::setCameraSize(QRect) {}

View file

@ -0,0 +1,20 @@
#ifndef QRCODEREADERBASE_H
#define QRCODEREADERBASE_H
#include <QObject>
#include <QRect>
class QRCodeReader: public QObject {
Q_OBJECT
public:
signals:
void codeReaded(QString code);
private:
void* m_qrCodeReader;
QRect m_cameraSize;
};
#endif // QRCODEREADERBASE_H

View file

@ -0,0 +1,110 @@
#include "QRCodeReaderBase.h"
//#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
//@interface QRCodeReaderImpl : UIViewController
//@end
/*@interface QRCodeReaderImpl () <AVCaptureMetadataOutputObjectsDelegate>
@property (nonatomic) QRCodeReader* qrCodeReader;
@property (nonatomic, strong) AVCaptureSession *captureSession;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewPlayer;
@end
@implementation QRCodeReaderImpl
//- (void)viewDidLoad {
// [super viewDidLoad];
// _captureSession = nil;
//}
//- (void)setQrCodeReader: (QRCodeReader*)value {
// _qrCodeReader = value;
//}
- (BOOL)startReading {
NSError *error;
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice: captureDevice error: &error];
if(!deviceInput) {
NSLog(@"Error %@", error.localizedDescription);
return NO;
}
_captureSession = [[AVCaptureSession alloc]init];
[_captureSession addInput:deviceInput];
AVCaptureMetadataOutput *capturedMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
[_captureSession addOutput:capturedMetadataOutput];
dispatch_queue_t dispatchQueue;
dispatchQueue = dispatch_queue_create("myQueue", NULL);
[capturedMetadataOutput setMetadataObjectsDelegate: self queue: dispatchQueue];
[capturedMetadataOutput setMetadataObjectTypes: [NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
_videoPreviewPlayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession: _captureSession];
//CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
QRect cameraRect = _qrCodeReader->cameraSize();
CGRect cameraCGRect = CGRectMake(cameraRect.x(),
cameraRect.y() + statusBarHeight,
cameraRect.width(),
cameraRect.height());
[_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspectFill];
[_videoPreviewPlayer setFrame: cameraCGRect];
// CALayer* layer = [UIApplication sharedApplication].keyWindow.layer;
[layer addSublayer: _videoPreviewPlayer];
[_captureSession startRunning];
return YES;
}
- (void)stopReading {
[_captureSession stopRunning];
_captureSession = nil;
[_videoPreviewPlayer removeFromSuperlayer];
}
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
if (metadataObjects != nil && metadataObjects.count > 0) {
AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects objectAtIndex:0];
if ([[metadataObject type] isEqualToString: AVMetadataObjectTypeQRCode]) {
_qrCodeReader->emit codeReaded([metadataObject stringValue].UTF8String);
}
}
}
@end
QRCodeReader::QRCodeReader() {
m_qrCodeReader = [[QRCodeReaderImpl alloc] init];
[m_qrCodeReader setQrCodeReader: this];
}
QRect QRCodeReader::cameraSize() {
return m_cameraSize;
}
void QRCodeReader::setCameraSize(QRect value) {
m_cameraSize = value;
}
void QRCodeReader::startReading() {
[m_qrCodeReader startReading];
}
void QRCodeReader::stopReading() {
[m_qrCodeReader stopReading];
}*/

View file

@ -0,0 +1,6 @@
#ifndef QTAPPDELEGATECINTERFACE_H
#define QTAPPDELEGATECINTERFACE_H
void QtAppDelegateInitialize();
#endif // QTAPPDELEGATECINTERFACE_H

View file

@ -0,0 +1,7 @@
//#import <UIKit/UIKit.h>
@interface QIOSApplicationDelegate
@end
@interface QIOSApplicationDelegate (AmneziaVPNDelegate)
@end

View file

@ -0,0 +1,61 @@
#import "QtAppDelegate.h"
#import "ios_controller.h"
#include <QFile>
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
/*- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
// Override point for customization after application launch.
NSLog(@"Application didFinishLaunchingWithOptions");
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
NSLog(@"In the background");
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
NSLog(@"In the foreground");
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// We will add content here soon.
NSLog(@"In the completionHandler");
}
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
if (url.fileURL) {
QString filePath(url.path.UTF8String);
if (filePath.isEmpty()) return NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(@"Application openURL: %@", url);
if (filePath.contains("backup")) {
IosController::Instance()->importBackupFromOutside(filePath);
} else {
QFile file(filePath);
bool isOpenFile = file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
IosController::Instance()->importConfigFromOutside(QString(data));
}
});
return YES;
}
return NO;
}*/
@end

View file

@ -0,0 +1,10 @@
//import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
}
class ScreenProtection {
}

View file

@ -0,0 +1,50 @@
import Foundation
import NetworkExtension
public func removeVPNC(_ vpncName: std.string) {
let vpncName = String(describing: vpncName)
Task {
await getManagers()?.first { manager in
if let name = manager.localizedDescription, name == vpncName {
Task {
await remove(manager)
}
return true
} else {
return false
}
}
}
}
public func clearSettings() {
Task {
await getManagers()?.forEach { manager in
Task {
await remove(manager)
}
}
}
}
func getManagers() async -> [NETunnelProviderManager]? {
do {
return try await NETunnelProviderManager.loadAllFromPreferences()
} catch {
log(.error, title: "VPNC: ", message: "loadAllFromPreferences error: \(error.localizedDescription)")
return nil
}
}
func remove(_ manager: NETunnelProviderManager) async {
let vpncName = manager.localizedDescription ?? "Unknown"
do {
try await manager.removeFromPreferences()
try await manager.loadFromPreferences()
log(.info, title: "VPNC: ", message: "Remove \(vpncName)")
} catch {
log(.error, title: "VPNC: ", message: "Failed to remove \(vpncName) (\(error.localizedDescription))")
}
}

View file

@ -0,0 +1,94 @@
import Foundation
struct WGConfig: Decodable {
let initPacketMagicHeader, responsePacketMagicHeader: String?
let underloadPacketMagicHeader, transportPacketMagicHeader: String?
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
let initPacketJunkSize, responsePacketJunkSize: String?
let dns1: String
let dns2: String
let mtu: String
let hostName: String
let port: Int
let clientIP: String
let clientPrivateKey: String
let serverPublicKey: String
let presharedKey: String?
var allowedIPs: [String]
var persistentKeepAlive: String
let splitTunnelType: Int
let splitTunnelSites: [String]
enum CodingKeys: String, CodingKey {
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
case dns1
case dns2
case mtu
case hostName
case port
case clientIP = "client_ip"
case clientPrivateKey = "client_priv_key"
case serverPublicKey = "server_pub_key"
case presharedKey = "psk_key"
case allowedIPs = "allowed_ips"
case persistentKeepAlive = "persistent_keep_alive"
case splitTunnelType
case splitTunnelSites
}
var settings: String {
junkPacketCount == nil ? "" :
"""
Jc = \(junkPacketCount!)
Jmin = \(junkPacketMinSize!)
Jmax = \(junkPacketMaxSize!)
S1 = \(initPacketJunkSize!)
S2 = \(responsePacketJunkSize!)
H1 = \(initPacketMagicHeader!)
H2 = \(responsePacketMagicHeader!)
H3 = \(underloadPacketMagicHeader!)
H4 = \(transportPacketMagicHeader!)
"""
}
var str: String {
"""
[Interface]
Address = \(clientIP)
DNS = \(dns1), \(dns2)
MTU = \(mtu)
PrivateKey = \(clientPrivateKey)
\(settings)
[Peer]
PublicKey = \(serverPublicKey)
\(presharedKey == nil ? "" : "PresharedKey = \(presharedKey!)")
AllowedIPs = \(allowedIPs.joined(separator: ", "))
Endpoint = \(hostName):\(port)
PersistentKeepalive = \(persistentKeepAlive)
"""
}
var redux: String {
"""
[Interface]
Address = \(clientIP)
DNS = \(dns1), \(dns2)
MTU = \(mtu)
PrivateKey = ***
\(settings)
[Peer]
PublicKey = ***
PresharedKey = ***
AllowedIPs = \(allowedIPs.joined(separator: ", "))
Endpoint = \(hostName):\(port)
PersistentKeepalive = \(persistentKeepAlive)
SplitTunnelType = \(splitTunnelType)
SplitTunnelSites = \(splitTunnelSites.joined(separator: ", "))
"""
}
}

View file

@ -0,0 +1,28 @@
/* 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 "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>
#define WG_KEY_LEN (32)
#define WG_KEY_LEN_BASE64 (45)
#define WG_KEY_LEN_HEX (65)
void key_to_base64(char base64[WG_KEY_LEN_BASE64],
const uint8_t key[WG_KEY_LEN]);
bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64);
void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]);
bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
void write_msg_to_log(const char* tag, const char* msg);
#import "TargetConditionals.h"
#if TARGET_OS_OSX
# include <libproc.h>
#endif

View file

@ -0,0 +1,99 @@
#ifndef IOS_CONTROLLER_H
#define IOS_CONTROLLER_H
#include "protocols/vpnprotocol.h"
#ifdef __OBJC__
#import <Foundation/Foundation.h>
@class NETunnelProviderManager;
#endif
using namespace amnezia;
struct Action
{
static const char *start;
static const char *restart;
static const char *stop;
static const char *getTunnelId;
static const char *getStatus;
};
struct MessageKey
{
static const char *action;
static const char *tunnelId;
static const char *config;
static const char *errorCode;
static const char *host;
static const char *port;
static const char *isOnDemand;
static const char *SplitTunnelType;
static const char *SplitTunnelSites;
};
class IosController : public QObject
{
Q_OBJECT
public:
static IosController *Instance();
virtual ~IosController() override = default;
bool initialize();
bool connectVpn(amnezia::Proto proto, const QJsonObject &configuration);
void disconnectVpn();
void vpnStatusDidChange(void *pNotification);
void vpnConfigurationDidChange(void *pNotification);
void getBackendLogs(std::function<void(const QString &)> &&callback);
void checkStatus();
bool shareText(const QStringList &filesToSend);
QString openFile();
void requestInetAccess();
signals:
void connectionStateChanged(Vpn::ConnectionState state);
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
void importConfigFromOutside(const QString);
void importBackupFromOutside(const QString);
void finished();
protected slots:
private:
explicit IosController();
bool setupOpenVPN();
bool setupCloak();
bool setupWireGuard();
bool setupAwg();
bool setupXray();
bool startOpenVPN(const QString &config);
bool startWireGuard(const QString &jsonConfig);
bool startXray(const QString &jsonConfig);
void startTunnel();
private:
void *m_iosControllerWrapper {};
#ifdef __OBJC__
NETunnelProviderManager *m_currentTunnel {};
NSString *m_serverAddress {};
bool isOurManager(NETunnelProviderManager *manager);
void sendVpnExtensionMessage(NSDictionary *message, std::function<void(NSDictionary *)> callback = nullptr);
#endif
amnezia::Proto m_proto;
QJsonObject m_rawConfig;
QString m_tunnelId;
uint64_t m_txBytes;
uint64_t m_rxBytes;
};
#endif // IOS_CONTROLLER_H

View file

@ -0,0 +1,851 @@
#include "ios_controller.h"
#include <QDebug>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QThread>
#include <QEventLoop>
#include "../protocols/vpnprotocol.h"
#import "ios_controller_wrapper.h"
#include <Security/Security.h>
const char* Action::start = "start";
const char* Action::restart = "restart";
const char* Action::stop = "stop";
const char* Action::getTunnelId = "getTunnelId";
const char* Action::getStatus = "status";
const char* MessageKey::action = "action";
const char* MessageKey::tunnelId = "tunnelId";
const char* MessageKey::config = "config";
const char* MessageKey::errorCode = "errorCode";
const char* MessageKey::host = "host";
const char* MessageKey::port = "port";
const char* MessageKey::isOnDemand = "is-on-demand";
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
//static UIViewController* getViewController() {
// NSArray *windows = [[UIApplication sharedApplication]windows];
// for (UIWindow *window in windows) {
// if (window.isKeyWindow) {
// return window.rootViewController;
// }
// }
// return nil;
//}
OSStatus requestAuthorization() {
AuthorizationRef authRef;
OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
if (status != errAuthorizationSuccess) {
qDebug() << "Authorization failed with status:" << status;
return status;
}
AuthorizationItem authItem = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights authRights = {1, &authItem};
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
status = AuthorizationCopyRights(authRef, &authRights, NULL, flags, NULL);
if (status != errAuthorizationSuccess) {
qDebug() << "Authorization rights copy failed with status:" << status;
}
return status;
}
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
switch (status) {
case NEVPNStatusInvalid:
return Vpn::ConnectionState::Unknown;
case NEVPNStatusDisconnected:
return Vpn::ConnectionState::Disconnected;
case NEVPNStatusConnecting:
return Vpn::ConnectionState::Connecting;
case NEVPNStatusConnected:
return Vpn::ConnectionState::Connected;
case NEVPNStatusReasserting:
return Vpn::ConnectionState::Connecting;
case NEVPNStatusDisconnecting:
return Vpn::ConnectionState::Disconnecting;
default:
return Vpn::ConnectionState::Unknown;
}
}
namespace {
IosController* s_instance = nullptr;
}
IosController::IosController() : QObject()
{
s_instance = this;
m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this];
[[NSNotificationCenter defaultCenter]
removeObserver: (__bridge NSObject *)m_iosControllerWrapper];
[[NSNotificationCenter defaultCenter]
addObserver: (__bridge NSObject *)m_iosControllerWrapper selector:@selector(vpnStatusDidChange:) name:NEVPNStatusDidChangeNotification object:nil];
[[NSNotificationCenter defaultCenter]
addObserver: (__bridge NSObject *)m_iosControllerWrapper selector:@selector(vpnConfigurationDidChange:) name:NEVPNConfigurationChangeNotification object:nil];
}
IosController* IosController::Instance() {
if (!s_instance) {
s_instance = new IosController();
}
return s_instance;
}
bool IosController::initialize()
{
if (requestAuthorization() != errAuthorizationSuccess) {
emit connectionStateChanged(Vpn::ConnectionState::Error);
return false;
}
__block bool ok = true;
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
@try {
if (error) {
qDebug() << "IosController::initialize : Error:" << [error.localizedDescription UTF8String];
emit connectionStateChanged(Vpn::ConnectionState::Error);
ok = false;
return;
}
NSInteger managerCount = managers.count;
qDebug() << "IosController::initialize : We have received managers:" << (long)managerCount;
for (NETunnelProviderManager *manager in managers) {
qDebug() << "IosController::initialize : VPNC: " << manager.localizedDescription;
if (manager.connection.status == NEVPNStatusConnected) {
m_currentTunnel = manager;
qDebug() << "IosController::initialize : VPN already connected with" << manager.localizedDescription;
emit connectionStateChanged(Vpn::ConnectionState::Connected);
break;
// TODO: show connected state
}
}
}
@catch (NSException *exception) {
qDebug() << "IosController::setTunnel : exception" << QString::fromNSString(exception.reason);
ok = false;
}
}];
return ok;
}
bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configuration)
{
m_proto = proto;
m_rawConfig = configuration;
m_serverAddress = configuration.value(config_key::hostName).toString().toNSString();
QString tunnelName;
if (configuration.value(config_key::description).toString().isEmpty()) {
tunnelName = QString("%1 %2")
.arg(configuration.value(config_key::hostName).toString())
.arg(ProtocolProps::protoToString(proto));
}
else {
tunnelName = QString("%1 (%2) %3")
.arg(configuration.value(config_key::description).toString())
.arg(configuration.value(config_key::hostName).toString())
.arg(ProtocolProps::protoToString(proto));
}
qDebug() << "IosController::connectVpn" << tunnelName;
m_currentTunnel = nullptr;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block bool ok = true;
__block bool isNewTunnelCreated = false;
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
@try {
if (error) {
qDebug() << "IosController::connectVpn : VPNC: loadAllFromPreferences error:" << [error.localizedDescription UTF8String];
emit connectionStateChanged(Vpn::ConnectionState::Error);
ok = false;
return;
}
NSInteger managerCount = managers.count;
qDebug() << "IosController::connectVpn : We have received managers:" << (long)managerCount;
for (NETunnelProviderManager *manager in managers) {
if ([manager.localizedDescription isEqualToString:tunnelName.toNSString()]) {
m_currentTunnel = manager;
qDebug() << "IosController::connectVpn : Using existing tunnel:" << manager.localizedDescription;
if (manager.connection.status == NEVPNStatusConnected) {
emit connectionStateChanged(Vpn::ConnectionState::Connected);
return;
}
break;
}
}
if (!m_currentTunnel) {
isNewTunnelCreated = true;
m_currentTunnel = [[NETunnelProviderManager alloc] init];
m_currentTunnel.localizedDescription = [NSString stringWithUTF8String:tunnelName.toStdString().c_str()];
qDebug() << "IosController::connectVpn : Creating new tunnel" << m_currentTunnel.localizedDescription;
}
}
@catch (NSException *exception) {
qDebug() << "IosController::connectVpn : exception" << QString::fromNSString(exception.reason);
ok = false;
m_currentTunnel = nullptr;
}
@finally {
dispatch_semaphore_signal(semaphore);
}
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (!ok) return false;
[[NSNotificationCenter defaultCenter]
removeObserver:(__bridge NSObject *)m_iosControllerWrapper];
[[NSNotificationCenter defaultCenter]
addObserver:(__bridge NSObject *)m_iosControllerWrapper
selector:@selector(vpnStatusDidChange:)
name:NEVPNStatusDidChangeNotification
object:m_currentTunnel.connection];
if (proto == amnezia::Proto::OpenVpn) {
return setupOpenVPN();
}
if (proto == amnezia::Proto::Cloak) {
return setupCloak();
}
if (proto == amnezia::Proto::WireGuard) {
return setupWireGuard();
}
if (proto == amnezia::Proto::Awg) {
return setupAwg();
}
if (proto == amnezia::Proto::Xray) {
return setupXray();
}
return false;
}
void IosController::disconnectVpn()
{
if (!m_currentTunnel) {
return;
}
if ([m_currentTunnel.connection isKindOfClass:[NETunnelProviderSession class]]) {
[(NETunnelProviderSession *)m_currentTunnel.connection stopTunnel];
}
}
void IosController::checkStatus()
{
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus];
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
sendVpnExtensionMessage(message, [&](NSDictionary* response){
uint64_t txBytes = [response[@"tx_bytes"] intValue];
uint64_t rxBytes = [response[@"rx_bytes"] intValue];
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
});
}
void IosController::vpnStatusDidChange(void *pNotification)
{
NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification;
if (session /* && session == TunnelManager.session */ ) {
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
if (session.status == NEVPNStatusDisconnected) {
if (@available(iOS 16.0, *)) {
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
switch (error.code) {
case NEVPNConnectionErrorOverslept:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the system slept for an extended period of time.";
break;
case NEVPNConnectionErrorNoNetworkAvailable:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the system is not connected to a network.";
break;
case NEVPNConnectionErrorUnrecoverableNetworkChange:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the network conditions changed in such a way that the VPN connection could not be maintained.";
break;
case NEVPNConnectionErrorConfigurationFailed:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the configuration is invalid. ";
break;
case NEVPNConnectionErrorServerAddressResolutionFailed:
qDebug() << "Disconnect error info" << "The address of the VPN server could not be determined.";
break;
case NEVPNConnectionErrorServerNotResponding:
qDebug() << "Disconnect error info" << "Network communication with the VPN server has failed.";
break;
case NEVPNConnectionErrorServerDead:
qDebug() << "Disconnect error info" << "The VPN server is no longer functioning.";
break;
case NEVPNConnectionErrorAuthenticationFailed:
qDebug() << "Disconnect error info" << "The user credentials were rejected by the VPN server.";
break;
case NEVPNConnectionErrorClientCertificateInvalid:
qDebug() << "Disconnect error info" << "The client certificate is invalid.";
break;
case NEVPNConnectionErrorClientCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The client certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorClientCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the client certificate has passed.";
break;
case NEVPNConnectionErrorPluginFailed:
qDebug() << "Disconnect error info" << "The VPN plugin died unexpectedly.";
break;
case NEVPNConnectionErrorConfigurationNotFound:
qDebug() << "Disconnect error info" << "The VPN configuration could not be found.";
break;
case NEVPNConnectionErrorPluginDisabled:
qDebug() << "Disconnect error info" << "The VPN plugin could not be found or needed to be updated.";
break;
case NEVPNConnectionErrorNegotiationFailed:
qDebug() << "Disconnect error info" << "The VPN protocol negotiation failed.";
break;
case NEVPNConnectionErrorServerDisconnected:
qDebug() << "Disconnect error info" << "The VPN server terminated the connection.";
break;
case NEVPNConnectionErrorServerCertificateInvalid:
qDebug() << "Disconnect error info" << "The server certificate is invalid.";
break;
case NEVPNConnectionErrorServerCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The server certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorServerCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the server certificate has passed.";
break;
default:
qDebug() << "Disconnect error info" << "Unknown code.";
break;
}
}
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
if (underlyingError != nil) {
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
switch (underlyingError.code) {
case 1:
qDebug() << "Disconnect underlying error" << "General. Use sysdiagnose.";
break;
case 2:
qDebug() << "Disconnect underlying error" << "Plug-in unavailable. Use sysdiagnose.";
break;
default:
qDebug() << "Disconnect underlying error" << "Unknown code. Use sysdiagnose.";
break;
}
}
}
} else {
qDebug() << "Disconnect error is absent";
}
}];
} else {
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
}
}
emit connectionStateChanged(iosStatusToState(session.status));
}
}
void IosController::vpnConfigurationDidChange(void *pNotification)
{
qDebug() << "IosController::vpnConfigurationDidChange" << pNotification;
}
bool IosController::setupOpenVPN()
{
QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject();
QString ovpnConfig = ovpn[config_key::config].toString();
QJsonObject openVPNConfig {};
openVPNConfig.insert(config_key::config, ovpnConfig);
if (ovpn.contains(config_key::mtu)) {
openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]);
} else {
openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu);
}
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
QJsonDocument openVPNConfigDoc(openVPNConfig);
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
return startOpenVPN(openVPNConfigStr);
}
bool IosController::setupCloak()
{
m_serverAddress = @"127.0.0.1";
QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject();
QString ovpnConfig = ovpn[config_key::config].toString();
QJsonObject cloak = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Cloak)].toObject();
cloak["NumConn"] = 1;
if (cloak.contains("remote")) {
cloak["RemoteHost"] = cloak["remote"].toString();
}
if (cloak.contains("port")) {
cloak["RemotePort"] = cloak["port"].toString();
}
cloak.remove("remote");
cloak.remove("port");
cloak.remove("transport_proto");
QJsonObject jsonObject {};
foreach(const QString& key, cloak.keys()) {
if(key == "NumConn" or key == "StreamTimeout"){
jsonObject.insert(key, cloak.value(key).toInt());
}else{
jsonObject.insert(key, cloak.value(key).toString());
}
}
QJsonDocument doc(jsonObject);
QString strJson(doc.toJson(QJsonDocument::Compact));
QString cloakBase64 = strJson.toUtf8().toBase64();
ovpnConfig.append("\n<cloak>\n");
ovpnConfig.append(cloakBase64);
ovpnConfig.append("\n</cloak>\n");
QJsonObject openVPNConfig {};
openVPNConfig.insert(config_key::config, ovpnConfig);
if (ovpn.contains(config_key::mtu)) {
openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]);
} else {
openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu);
}
openVPNConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
QJsonDocument openVPNConfigDoc(openVPNConfig);
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
return startOpenVPN(openVPNConfigStr);
}
bool IosController::setupWireGuard()
{
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject();
QJsonObject wgConfig {};
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
if (config.contains(config_key::mtu)) {
wgConfig.insert(config_key::mtu, config[config_key::mtu]);
} else {
wgConfig.insert(config_key::mtu, protocols::wireguard::defaultMtu);
}
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
wgConfig.insert(config_key::port, config[config_key::port]);
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) {
wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]);
} else {
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
wgConfig.insert(config_key::allowed_ips, allowed_ips);
}
if (config.contains(config_key::persistent_keep_alive)) {
wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]);
} else {
wgConfig.insert(config_key::persistent_keep_alive, "25");
}
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
return startWireGuard(wgConfigDocStr);
}
bool IosController::setupXray()
{
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Xray)].toObject();
QJsonDocument xrayConfigDoc(config);
QString xrayConfigStr(xrayConfigDoc.toJson(QJsonDocument::Compact));
return startXray(xrayConfigStr);
}
bool IosController::setupAwg()
{
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject();
QJsonObject wgConfig {};
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
if (config.contains(config_key::mtu)) {
wgConfig.insert(config_key::mtu, config[config_key::mtu]);
} else {
wgConfig.insert(config_key::mtu, protocols::awg::defaultMtu);
}
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
wgConfig.insert(config_key::port, config[config_key::port]);
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
wgConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) {
wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]);
} else {
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
wgConfig.insert(config_key::allowed_ips, allowed_ips);
}
if (config.contains(config_key::persistent_keep_alive)) {
wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]);
} else {
wgConfig.insert(config_key::persistent_keep_alive, "25");
}
wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]);
wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]);
wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]);
wgConfig.insert(config_key::transportPacketMagicHeader, config[config_key::transportPacketMagicHeader]);
wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]);
wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]);
wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]);
wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]);
wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]);
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
return startWireGuard(wgConfigDocStr);
}
bool IosController::startOpenVPN(const QString &config)
{
qDebug() << "IosController::startOpenVPN";
NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init];
tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID];
tunnelProtocol.providerConfiguration = @{@"ovpn": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]};
tunnelProtocol.serverAddress = m_serverAddress;
m_currentTunnel.protocolConfiguration = tunnelProtocol;
startTunnel();
}
bool IosController::startWireGuard(const QString &config)
{
qDebug() << "IosController::startWireGuard";
NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init];
tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID];
tunnelProtocol.providerConfiguration = @{@"wireguard": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]};
tunnelProtocol.serverAddress = m_serverAddress;
m_currentTunnel.protocolConfiguration = tunnelProtocol;
startTunnel();
}
bool IosController::startXray(const QString &config)
{
qDebug() << "IosController::startXray";
NETunnelProviderProtocol *tunnelProtocol = [[NETunnelProviderProtocol alloc] init];
tunnelProtocol.providerBundleIdentifier = [NSString stringWithUTF8String:VPN_NE_BUNDLEID];
tunnelProtocol.providerConfiguration = @{@"xray": [[NSString stringWithUTF8String:config.toStdString().c_str()] dataUsingEncoding:NSUTF8StringEncoding]};
tunnelProtocol.serverAddress = m_serverAddress;
m_currentTunnel.protocolConfiguration = tunnelProtocol;
startTunnel();
}
void IosController::startTunnel()
{
NSString *protocolName = @"Unknown";
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
protocolName = @"WireGuard";
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
protocolName = @"OpenVPN";
}
m_rxBytes = 0;
m_txBytes = 0;
[m_currentTunnel setEnabled:YES];
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (saveError) {
qDebug().nospace() << "IosController::startTunnel" << protocolName << ": Connect " << protocolName << " Tunnel Save Error" << saveError.localizedDescription.UTF8String;
emit connectionStateChanged(Vpn::ConnectionState::Error);
return;
}
[m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
if (loadError) {
qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << ": Connect " << protocolName << " Tunnel Load Error" << loadError.localizedDescription.UTF8String;
emit connectionStateChanged(Vpn::ConnectionState::Error);
return;
}
NSError *startError = nil;
qDebug() << iosStatusToState(m_currentTunnel.connection.status);
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
if (!started || startError) {
qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << " : Connect " << protocolName << " Tunnel Start Error"
<< (startError ? startError.localizedDescription.UTF8String : "");
emit connectionStateChanged(Vpn::ConnectionState::Error);
} else {
qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << " : Starting the tunnel succeeded";
}
}];
});
}];
}
bool IosController::isOurManager(NETunnelProviderManager* manager) {
NETunnelProviderProtocol* tunnelProto = (NETunnelProviderProtocol*)manager.protocolConfiguration;
if (!tunnelProto) {
qDebug() << "Ignoring manager because the proto is invalid";
return false;
}
if (!tunnelProto.providerBundleIdentifier) {
qDebug() << "Ignoring manager because the bundle identifier is null";
return false;
}
if (![tunnelProto.providerBundleIdentifier isEqualToString:[NSString stringWithUTF8String:VPN_NE_BUNDLEID]]) {
qDebug() << "Ignoring manager because the bundle identifier doesn't match";
return false;
}
qDebug() << "Found the manager with the correct bundle identifier:" << QString::fromNSString(tunnelProto.providerBundleIdentifier);
return true;
}
void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function<void(NSDictionary*)> callback)
{
if (!m_currentTunnel) {
qDebug() << "Cannot set an extension callback without a tunnel manager";
return;
}
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:message options:0 error:&error];
if (!data || error) {
qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:"
<< [error.localizedDescription UTF8String];
return;
}
void (^completionHandler)(NSData *) = ^(NSData *responseData) {
if (!responseData) {
if (callback) callback(nil);
return;
}
NSError *deserializeError = nil;
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&deserializeError];
if (response && [response isKindOfClass:[NSDictionary class]]) {
if (callback) callback(response);
return;
} else if (deserializeError) {
qDebug() << "Failed to deserialize the VpnExtension response";
}
if (callback) callback(nil);
};
NETunnelProviderSession *session = (NETunnelProviderSession *)m_currentTunnel.connection;
NSError *sendError = nil;
if ([session respondsToSelector:@selector(sendProviderMessage:returnError:responseHandler:)]) {
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
} else {
qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist";
}
if (sendError) {
qDebug() << "Failed to send message to VpnExtension. Error:"
<< [sendError.localizedDescription UTF8String];
}
}
bool IosController::shareText(const QStringList& filesToSend) {
NSMutableArray *sharingItems = [NSMutableArray new];
for (int i = 0; i < filesToSend.size(); i++) {
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
[sharingItems addObject:logFileUrl];
}
// UIViewController *qtController = getViewController();
// if (!qtController) return;
// UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
__block bool isAccepted = false;
// [activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
// isAccepted = completed;
// emit finished();
// }];
// [qtController presentViewController:activityController animated:YES completion:nil];
// UIPopoverPresentationController *popController = activityController.popoverPresentationController;
// if (popController) {
// popController.sourceView = qtController.view;
// popController.sourceRect = CGRectMake(100, 100, 100, 100);
// }
QEventLoop wait;
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
wait.exec();
return isAccepted;
}
QString IosController::openFile() {
// UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
// DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
// documentPicker.delegate = documentPickerDelegate;
// UIViewController *qtController = getViewController();
// if (!qtController) return;
// [qtController presentViewController:documentPicker animated:YES completion:nil];
__block QString filePath;
// documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
// if (path) {
// filePath = QString::fromUtf8(path.UTF8String);
// } else {
// filePath = QString();
// }
// emit finished();
// };
QEventLoop wait;
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
wait.exec();
return filePath;
}
void IosController::requestInetAccess() {
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
if (url) {
qDebug() << "IosController::requestInetAccess URL error";
return;
}
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
qDebug() << "IosController::requestInetAccess error:" << error.localizedDescription;
} else {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
QString responseBody = QString::fromUtf8((const char*)data.bytes, data.length);
qDebug() << "IosController::requestInetAccess server response:" << httpResponse.statusCode << "\n\n" <<responseBody;
}
}];
[task resume];
}

View file

@ -0,0 +1,25 @@
#import <NetworkExtension/NetworkExtension.h>
#import <NetworkExtension/NETunnelProviderSession.h>
#import <Foundation/Foundation.h>
//#include <UIKit/UIKit.h>
#include <Security/Security.h>
class IosController;
@interface IosControllerWrapper : NSObject {
IosController *cppController;
}
- (instancetype)initWithCppController:(IosController *)controller;
- (void)vpnStatusDidChange:(NSNotification *)notification;
- (void)vpnConfigurationDidChange:(NSNotification *)notification;
@end
typedef void (^DocumentPickerClosedCallback)(NSString *path);
//@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
//@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
//@end

View file

@ -0,0 +1,45 @@
#import "ios_controller_wrapper.h"
#include "ios_controller.h"
@implementation IosControllerWrapper
- (instancetype)initWithCppController:(IosController *)controller {
self = [super init];
if (self) {
cppController = controller;
}
return self;
}
- (void)vpnStatusDidChange:(NSNotification *)notification {
NETunnelProviderSession *session = (NETunnelProviderSession *)notification.object;
if (session ) {
cppController->vpnStatusDidChange(session);
}
}
- (void) vpnConfigurationDidChange:(NSNotification *)notification {
// cppController->vpnStatusDidChange(notification);
}
@end
//@implementation DocumentPickerDelegate
//- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
// for (NSURL *url in urls) {
// if (self.documentPickerClosedCallback) {
// self.documentPickerClosedCallback([url path]);
// }
// }
//}
//- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
// if (self.documentPickerClosedCallback) {
// self.documentPickerClosedCallback(nil);
// }
//}
//@end

View file

@ -0,0 +1,244 @@
/* 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/. */
// This file contains all the C functions needed by the Wireguard swift code.
#include <stdlib.h>
#include <string.h>
#ifndef NETWORK_EXTENSION
//# include "logger.h"
#else
# import <Foundation/Foundation.h>
# import <os/log.h>
#endif
#define MAX_LOG_FILE_SIZE 204800
// Key base64/hex functions
// ------------------------
#define WG_KEY_LEN (32)
#define WG_KEY_LEN_BASE64 (45)
#define WG_KEY_LEN_HEX (65)
#define EXPORT __attribute__((visibility("default")))
extern "C" {
EXPORT void key_to_base64(char base64[WG_KEY_LEN_BASE64], const uint8_t key[WG_KEY_LEN]);
EXPORT bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64);
EXPORT void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]);
EXPORT bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
EXPORT bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
EXPORT void write_msg_to_log(const char* tag, const char* msg);
}
EXPORT void key_to_base64(char base64[WG_KEY_LEN_BASE64], const uint8_t key[WG_KEY_LEN]) {
const char range[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const char padchar = '=';
int padlen = 0;
char* out = base64;
const uint8_t* in = key;
for (int i = 0; i < WG_KEY_LEN;) {
int chunk = 0;
chunk |= int(in[i++]) << 16;
if (i == WG_KEY_LEN) {
padlen = 2;
} else {
chunk |= int(in[i++]) << 8;
if (i == WG_KEY_LEN) {
padlen = 1;
} else {
chunk |= int(in[i++]);
}
}
int j = (chunk & 0x00fc0000) >> 18;
int k = (chunk & 0x0003f000) >> 12;
int l = (chunk & 0x00000fc0) >> 6;
int m = (chunk & 0x0000003f);
*out++ = range[j];
*out++ = range[k];
if (padlen > 1) {
*out++ = padchar;
} else {
*out++ = range[l];
}
if (padlen > 0) {
*out++ = padchar;
} else {
*out++ = range[m];
}
}
base64[WG_KEY_LEN_BASE64 - 1] = 0;
}
EXPORT bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64) {
if (strlen(base64) != WG_KEY_LEN_BASE64 - 1 || base64[WG_KEY_LEN_BASE64 - 2] != '=') {
return false;
}
unsigned int buf = 0;
int nbits = 0;
uint8_t* out = key;
int offset = 0;
for (int i = 0; i < WG_KEY_LEN_BASE64; ++i) {
int ch = base64[i];
int d;
if (ch >= 'A' && ch <= 'Z') {
d = ch - 'A';
} else if (ch >= 'a' && ch <= 'z') {
d = ch - 'a' + 26;
} else if (ch >= '0' && ch <= '9') {
d = ch - '0' + 52;
} else if (ch == '+') {
d = 62;
} else if (ch == '/') {
d = 63;
} else {
d = -1;
}
if (d != -1) {
buf = (buf << 6) | d;
nbits += 6;
if (nbits >= 8) {
nbits -= 8;
out[offset++] = buf >> nbits;
buf &= (1 << nbits) - 1;
}
}
}
return true;
}
inline char toHex(uint8_t value) { return "0123456789abcdef"[value & 0xF]; }
inline int fromHex(uint8_t c) {
return ((c >= '0') && (c <= '9'))
? int(c - '0')
: ((c >= 'A') && (c <= 'F')) ? int(c - 'A' + 10)
: ((c >= 'a') && (c <= 'f')) ? int(c - 'a' + 10) : -1;
}
EXPORT void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]) {
char* hexData = hex;
const unsigned char* data = (const unsigned char*)key;
for (int i = 0, o = 0; i < WG_KEY_LEN; ++i) {
hexData[o++] = toHex(data[i] >> 4);
hexData[o++] = toHex(data[i] & 0xf);
}
hex[WG_KEY_LEN_HEX - 1] = 0;
}
EXPORT bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex) {
if (strlen(hex) != WG_KEY_LEN_HEX - 1) {
return false;
}
bool odd_digit = true;
unsigned char* result = (unsigned char*)key + WG_KEY_LEN;
for (int i = WG_KEY_LEN_HEX - 1; i >= 0; --i) {
int tmp = fromHex((unsigned char)(hex[i]));
if (tmp == -1) {
continue;
}
if (odd_digit) {
--result;
*result = tmp;
odd_digit = false;
} else {
*result |= tmp << 4;
odd_digit = true;
}
}
return true;
}
EXPORT bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]) {
for (int i = 0; i < WG_KEY_LEN; i++) {
if (key1[i] != key2[i]) {
return false;
}
}
return true;
}
// Logging functions
// -----------------
EXPORT void write_msg_to_log(const char* tag, const char* msg) {
#ifndef NETWORK_EXTENSION
// logger.debug() << "Swift log - tag:" << tag << "msg: " << msg;
#else
os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "tag: %s - msg: %s", tag, msg);
@autoreleasepool {
NSString* groupId = [NSString stringWithUTF8String:GROUP_ID];
NSURL* groupPath =
[[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId];
NSURL* pathUrl = [groupPath URLByAppendingPathComponent:@"networkextension.log"];
NSString* path = [pathUrl path];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
} else {
NSError* error = nil;
NSDictionary* fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path
error:&error];
if (error) {
return;
}
NSNumber* fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
long long fileSize = [fileSizeNumber longLongValue];
if (fileSize > MAX_LOG_FILE_SIZE) {
[[NSFileManager defaultManager] removeItemAtPath:path error:&error];
[[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil];
}
}
NSError* error = nil;
NSFileHandle* fh = [NSFileHandle fileHandleForWritingToURL:pathUrl error:&error];
if (!fh) {
return;
}
NSString* dateString = [NSDateFormatter localizedStringFromDate:[NSDate date]
dateStyle:NSDateFormatterShortStyle
timeStyle:NSDateFormatterFullStyle];
NSString* str = [NSString stringWithFormat:@" - %s\n", msg];
NSData* data =
[[dateString stringByAppendingString:str] dataUsingEncoding:NSUTF8StringEncoding];
@try {
[fh seekToEndOfFile];
[fh writeData:data];
} @catch (NSException* exception) {
}
[fh closeFile];
}
#endif
}

View file

@ -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 IOSNETWORKWATCHER_H
#define IOSNETWORKWATCHER_H
#include <Network/Network.h>
#include "networkwatcherimpl.h"
class IOSNetworkWatcher : public NetworkWatcherImpl {
public:
explicit IOSNetworkWatcher(QObject* parent);
~IOSNetworkWatcher();
void initialize() 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

View file

@ -0,0 +1,69 @@
/* 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 <Network/Network.h>
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::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
}

View file

@ -0,0 +1,28 @@
/* 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 IOSNOTIFICATIONHANDLER_H
#define IOSNOTIFICATIONHANDLER_H
#include "ui/notificationhandler.h"
#include <QObject>
class IOSNotificationHandler final : public NotificationHandler {
Q_DISABLE_COPY_MOVE(IOSNotificationHandler)
public:
IOSNotificationHandler(QObject* parent);
~IOSNotificationHandler();
protected:
void notify(Message type, const QString& title, const QString& message,
int timerMsec) override;
private:
void* m_delegate = nullptr;
};
#endif // IOSNOTIFICATIONHANDLER_H

View file

@ -0,0 +1,90 @@
/* 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/iosnotificationhandler.h"
#import <UserNotifications/UserNotifications.h>
#import <Foundation/Foundation.h>
//#import <UIKit/UIKit.h>
/*
@interface IOSNotificationDelegate
: UIResponder <UIApplicationDelegate, UNUserNotificationCenterDelegate> {
IOSNotificationHandler* m_iosNotificationHandler;
}
@end
@implementation IOSNotificationDelegate
- (id)initWithObject:(IOSNotificationHandler*)notification {
self = [super init];
if (self) {
m_iosNotificationHandler = notification;
}
return self;
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
willPresentNotification:(UNNotification*)notification
withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler {
Q_UNUSED(center)
completionHandler(UNNotificationPresentationOptionAlert);
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
didReceiveNotificationResponse:(UNNotificationResponse*)response
withCompletionHandler:(void (^)())completionHandler {
Q_UNUSED(center)
Q_UNUSED(response)
completionHandler();
}
@end
IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) {
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError* _Nullable error) {
Q_UNUSED(granted);
if (!error) {
m_delegate = [[IOSNotificationDelegate alloc] initWithObject:this];
}
}];
}
IOSNotificationHandler::~IOSNotificationHandler() { }
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
const QString& message, int timerMsec) {
Q_UNUSED(type);
if (!m_delegate) {
return;
}
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = title.toNSString();
content.body = message.toNSString();
content.sound = [UNNotificationSound defaultSound];
int timerSec = timerMsec / 1000;
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
content:content
trigger:trigger];
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = id(m_delegate);
[center addNotificationRequest:request
withCompletionHandler:^(NSError* _Nullable error) {
if (error) {
NSLog(@"Local Notification failed");
}
}];
}*/