iOS Wireguard
3
.gitmodules
vendored
|
@ -4,3 +4,6 @@
|
|||
[submodule "client/3rd/wireguard-tools"]
|
||||
path = client/3rd/wireguard-tools
|
||||
url = https://github.com/WireGuard/wireguard-tools/
|
||||
[submodule "client/3rd/wireguard-apple"]
|
||||
path = client/3rd/wireguard-apple
|
||||
url = https://github.com/WireGuard/wireguard-apple
|
||||
|
|
1
client/3rd/wireguard-apple
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 23618f994f17d8ad8f2f65d79b4a1e8a0830b334
|
|
@ -68,6 +68,10 @@ HEADERS += \
|
|||
utils.h \
|
||||
vpnconnection.h \
|
||||
protocols/vpnprotocol.h \
|
||||
logger.h \
|
||||
loghandler.h \
|
||||
loglevel.h \
|
||||
constants.h
|
||||
|
||||
SOURCES += \
|
||||
configurators/cloak_configurator.cpp \
|
||||
|
@ -116,6 +120,9 @@ SOURCES += \
|
|||
utils.cpp \
|
||||
vpnconnection.cpp \
|
||||
protocols/vpnprotocol.cpp \
|
||||
logger.cpp \
|
||||
loghandler.cpp
|
||||
|
||||
|
||||
RESOURCES += \
|
||||
resources.qrc
|
||||
|
@ -238,6 +245,42 @@ android {
|
|||
ios {
|
||||
message("Client ios build")
|
||||
CONFIG += static
|
||||
CONFIG += file_copies
|
||||
|
||||
# For the authentication
|
||||
LIBS += -framework AuthenticationServices
|
||||
|
||||
# For notifications
|
||||
LIBS += -framework UIKit
|
||||
LIBS += -framework Foundation
|
||||
LIBS += -framework StoreKit
|
||||
LIBS += -framework UserNotifications
|
||||
|
||||
DEFINES += MVPN_IOS
|
||||
|
||||
SOURCES += \
|
||||
# platforms/macos/macospingsender.cpp
|
||||
|
||||
OBJECTIVE_SOURCES += \
|
||||
# platforms/ios/iosiaphandler.mm \
|
||||
# platforms/ios/iosauthenticationlistener.mm \
|
||||
# platforms/ios/ioscontroller.mm \
|
||||
# platforms/ios/iosdatamigration.mm \
|
||||
platforms/ios/iosglue.mm \
|
||||
# platforms/ios/iosnotificationhandler.mm \
|
||||
# platforms/ios/iosutils.mm \
|
||||
# platforms/macos/macoscryptosettings.mm
|
||||
|
||||
HEADERS += \
|
||||
# platforms/macos/macospingsender.h
|
||||
|
||||
OBJECTIVE_HEADERS += \
|
||||
# platforms/ios/iosiaphandler.h \
|
||||
# platforms/ios/iosauthenticationlistener.h \
|
||||
# platforms/ios/ioscontroller.h \
|
||||
# platforms/ios/iosdatamigration.h \
|
||||
# platforms/ios/iosnotificationhandler.h \
|
||||
# platforms/ios/iosutils.h
|
||||
|
||||
Q_ENABLE_BITCODE.value = NO
|
||||
Q_ENABLE_BITCODE.name = ENABLE_BITCODE
|
||||
|
@ -278,6 +321,15 @@ ios {
|
|||
LIBS += $$PWD/3rd/OpenSSL/lib/ios/simulator/libcrypto.a
|
||||
LIBS += $$PWD/3rd/OpenSSL/lib/ios/simulator/libssl.a
|
||||
}
|
||||
|
||||
NETWORKEXTENSION=1
|
||||
! build_pass: system(ruby $$PWD/ios/xcode_patcher.rb "$$PWD" "$$OUT_PWD/AmneziaVPN.xcodeproj" "2.0" "2.0.0" "ios" "$$NETWORKEXTENSION"|| echo "Failed to merge xcode with wireguard")
|
||||
|
||||
|
||||
|
||||
#ruby %{sourceDir}/client/ios/xcode_patcher.rb "%{buildDir}/AmneziaVPN.xcodeproj" "2.0" "2.0.0" "ios" "1"
|
||||
#cd client/ && /Users/md/Qt/5.15.2/ios/bin/qmake -o Makefile /Users/md/amnezia/desktop-client/client/client.pro -spec macx-ios-clang CONFIG+=iphonesimulator CONFIG+=simulator CONFIG+=qml_debug -after
|
||||
# %{sourceDir}/client/ios/xcode_patcher.rb %{buildDir}/client/AmneziaVPN.xcodeproj 2.0 2.0.0 ios 1
|
||||
}
|
||||
|
||||
|
||||
|
|
127
client/constants.h
Normal file
|
@ -0,0 +1,127 @@
|
|||
/* 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 CONSTANTS_H
|
||||
#define CONSTANTS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Constants {
|
||||
|
||||
// Returns true if we are in a production environment.
|
||||
bool inProduction();
|
||||
void setStaging();
|
||||
|
||||
// Number of msecs for the captive-portal block alert.
|
||||
constexpr uint32_t CAPTIVE_PORTAL_ALERT_MSEC = 4000;
|
||||
|
||||
// Number of msecs for the unsecured network alert.
|
||||
constexpr uint32_t UNSECURED_NETWORK_ALERT_MSEC = 4000;
|
||||
|
||||
// Number of recent connections to retain.
|
||||
constexpr int RECENT_CONNECTIONS_MAX_COUNT = 5;
|
||||
|
||||
#if defined(UNIT_TEST)
|
||||
# define CONSTEXPR(type, functionName, releaseValue, debugValue, \
|
||||
testingValue) \
|
||||
inline type functionName() { return testingValue; }
|
||||
#else
|
||||
# define CONSTEXPR(type, functionName, releaseValue, debugValue, \
|
||||
testingValue) \
|
||||
inline type functionName() { \
|
||||
return inProduction() ? releaseValue : debugValue; \
|
||||
}
|
||||
#endif
|
||||
|
||||
// Let's refresh the IP address any 10 minutes (in milliseconds).
|
||||
CONSTEXPR(uint32_t, ipAddressTimerMsec, 600000, 10000, 0)
|
||||
|
||||
// Let's check the connection status any second.
|
||||
CONSTEXPR(uint32_t, checkStatusTimerMsec, 1000, 1000, 0)
|
||||
|
||||
// Number of points for the charts.
|
||||
CONSTEXPR(int, chartsMaxPoints, 30, 30, 30);
|
||||
|
||||
// Any 6 hours, a new check
|
||||
CONSTEXPR(uint32_t, releaseMonitorMsec, 21600000, 4000, 0)
|
||||
|
||||
// in milliseconds, how often we should fetch the server list and the account.
|
||||
CONSTEXPR(uint32_t, scheduleAccountAndServersTimerMsec, 3600000, 4000, 0)
|
||||
|
||||
// how often we check the captive portal when the VPN is on.
|
||||
CONSTEXPR(uint32_t, captivePortalRequestTimeoutMsec, 10000, 4000, 0)
|
||||
|
||||
// How fast the animated icon should move
|
||||
CONSTEXPR(uint32_t, statusIconAnimationMsec, 200, 200, 0)
|
||||
|
||||
// How often glean pings are sent
|
||||
CONSTEXPR(uint32_t, gleanTimeoutMsec, 1200000, 1000, 0)
|
||||
|
||||
// How often we check the surveys to be executed (no network requests are done
|
||||
// for this check)
|
||||
CONSTEXPR(uint32_t, surveyTimerMsec, 300000, 4000, 0)
|
||||
|
||||
#undef CONSTEXPR
|
||||
|
||||
#define PRODBETAEXPR(type, functionName, prod, beta) \
|
||||
inline type functionName() { return inProduction() ? prod : beta; }
|
||||
|
||||
constexpr const char* API_PRODUCTION_URL = "https://vpn.mozilla.org";
|
||||
constexpr const char* API_STAGING_URL =
|
||||
"https://stage-vpn.guardian.nonprod.cloudops.mozgcp.net";
|
||||
|
||||
constexpr const char* LOGO_URL = ":/ui/resources/logo-dock.png";
|
||||
|
||||
PRODBETAEXPR(const char*, fxaUrl, "https://api.accounts.firefox.com",
|
||||
"https://api-accounts.stage.mozaws.net")
|
||||
PRODBETAEXPR(
|
||||
const char*, balrogUrl,
|
||||
"https://aus5.mozilla.org/json/1/FirefoxVPN/%1/%2/release/update.json",
|
||||
"https://stage.balrog.nonprod.cloudops.mozgcp.net/json/1/FirefoxVPN/%1/%2/"
|
||||
"release-cdntest/update.json");
|
||||
PRODBETAEXPR(
|
||||
const char*, balrogRootCertFingerprint,
|
||||
"97e8ba9cf12fb3de53cc42a4e6577ed64df493c247b414fea036818d3823560e",
|
||||
"3c01446abe9036cea9a09acaa3a520ac628f20a7ae32ce861cb2efb70fa0c745");
|
||||
|
||||
#undef PRODBETAEXPR
|
||||
|
||||
constexpr const char* PLATFORM_NAME =
|
||||
#if defined(MVPN_IOS)
|
||||
"ios"
|
||||
#elif defined(MVPN_MACOS)
|
||||
"macos"
|
||||
#elif defined(MVPN_LINUX)
|
||||
"linux"
|
||||
#elif defined(MVPN_ANDROID)
|
||||
"android"
|
||||
#elif defined(MVPN_WINDOWS)
|
||||
"windows"
|
||||
#elif defined(UNIT_TEST) || defined(MVPN_DUMMY)
|
||||
"dummy"
|
||||
#else
|
||||
# error "Unsupported platform"
|
||||
#endif
|
||||
;
|
||||
|
||||
constexpr const char* PLACEHOLDER_USER_DNS = "127.0.0.1";
|
||||
|
||||
#if defined(MVPN_ADJUST)
|
||||
// These are the two auto-generated token from the Adjust dashboard for the
|
||||
// "Subscription Completed" event. We have two since in the Adjust dashboard we
|
||||
// have defined two apps for iOS and Android with a event token each.
|
||||
constexpr const char* ADJUST_SUBSCRIPTION_COMPLETED =
|
||||
# if defined(MVPN_IOS)
|
||||
"jl72xm"
|
||||
# elif defined(MVPN_ANDROID)
|
||||
"o1mn9m"
|
||||
# else
|
||||
""
|
||||
# endif
|
||||
;
|
||||
#endif
|
||||
|
||||
}; // namespace Constants
|
||||
|
||||
#endif // CONSTANTS_H
|
43
client/ios/app/AmneziaVPNLaunchScreen.storyboard
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="ipad12_9rounded" orientation="portrait" layout="fullscreen" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="gZ9-gc-3t5">
|
||||
<rect key="frame" x="0.0" y="0.0" width="1024" height="1366"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="launch.png" translatesAutoresizingMaskIntoConstraints="NO" id="q5g-aV-39U">
|
||||
<rect key="frame" x="467" y="638" width="90" height="90"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="90" id="VFp-nz-h8O"/>
|
||||
<constraint firstAttribute="height" constant="90" id="ZUg-Ud-mgE"/>
|
||||
</constraints>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<viewLayoutGuide key="safeArea" id="Whf-X3-AA4"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstItem="q5g-aV-39U" firstAttribute="centerX" secondItem="gZ9-gc-3t5" secondAttribute="centerX" id="Ayw-bo-LVF"/>
|
||||
<constraint firstItem="q5g-aV-39U" firstAttribute="centerY" secondItem="gZ9-gc-3t5" secondAttribute="centerY" id="YHd-Kc-J0u"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="launch.png" width="1024" height="1024"/>
|
||||
</resources>
|
||||
</document>
|
116
client/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json
Normal file
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon-ios-20@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-20@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-29@2x-1.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-29@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-40@2x-1.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-40@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-60@2x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "2x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-60@3x.png",
|
||||
"idiom" : "iphone",
|
||||
"scale" : "3x",
|
||||
"size" : "60x60"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-20@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-20@2x-1.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "20x20"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-29@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-29@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "29x29"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-40@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-40@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "40x40"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-76@1x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "1x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-76@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "76x76"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-83.5@2x.png",
|
||||
"idiom" : "ipad",
|
||||
"scale" : "2x",
|
||||
"size" : "83.5x83.5"
|
||||
},
|
||||
{
|
||||
"filename" : "icon-ios-1024@1x.png",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 407 B |
After Width: | Height: | Size: 721 B |
After Width: | Height: | Size: 721 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 596 B |
After Width: | Height: | Size: 975 B |
After Width: | Height: | Size: 975 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 744 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
6
client/ios/app/Images.xcassets/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
55
client/ios/app/Info.plist
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Mozilla VPN</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict/>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
<dict/>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${APP_DISPLAY_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<false/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>AmneziaVPNLaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array/>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array/>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
<string>group.org.mozilla.ios.Guardian</string>
|
||||
<key>ADJUST_SDK_TOKEN</key>
|
||||
<string>$(ADJUST_SDK_TOKEN)</string>
|
||||
</dict>
|
||||
</plist>
|
BIN
client/ios/app/launch.png
Normal file
After Width: | Height: | Size: 12 KiB |
16
client/ios/app/main.entitlements
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?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>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(GROUP_ID_IOS)</string>
|
||||
</array>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,14 @@
|
|||
<?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>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(GROUP_ID_IOS)</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
13
client/ios/xcode.xconfig
Normal file
|
@ -0,0 +1,13 @@
|
|||
DEVELOPMENT_TEAM = <X7UJ388FXK>
|
||||
|
||||
# MacOS configuration
|
||||
GROUP_ID_MACOS = <>
|
||||
APP_ID_MACOS = <>
|
||||
NETEXT_ID_MACOS = <>
|
||||
LOGIN_ID_MACOS = <>
|
||||
NATIVEMESSAGING_ID_MACOS = <>
|
||||
|
||||
# IOS configuration
|
||||
GROUP_ID_IOS = group.org.mozilla.ios.Guardian
|
||||
APP_ID_IOS = org.mozilla.ios.FirefoxVPN
|
||||
NETEXT_ID_IOS = org.mozilla.ios.FirefoxVPN.network-extension
|
598
client/ios/xcode_patcher.rb
Normal file
|
@ -0,0 +1,598 @@
|
|||
# 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/.
|
||||
|
||||
require 'xcodeproj'
|
||||
|
||||
class XCodeprojPatcher
|
||||
attr :project
|
||||
attr :p_root
|
||||
attr :target_main
|
||||
attr :target_extension
|
||||
|
||||
# @@project_root
|
||||
# def self.project_root
|
||||
# @@project_root
|
||||
# end
|
||||
|
||||
def run(project_root, file, shortVersion, fullVersion, platform, networkExtension, webExtension, configHash, adjust_sdk_token)
|
||||
@p_root = project_root
|
||||
open_project file
|
||||
open_target_main
|
||||
|
||||
die 'IOS requires networkExtension mode' if not networkExtension and platform == 'ios'
|
||||
|
||||
group = @project.main_group.new_group('Configuration')
|
||||
@configFile = group.new_file('ios/xcode.xconfig')
|
||||
|
||||
setup_target_main shortVersion, fullVersion, platform, networkExtension, configHash, adjust_sdk_token
|
||||
|
||||
if platform == 'macos'
|
||||
setup_target_loginitem shortVersion, fullVersion, configHash
|
||||
setup_target_nativemessaging shortVersion, fullVersion, configHash if webExtension
|
||||
end
|
||||
|
||||
if networkExtension
|
||||
setup_target_extension shortVersion, fullVersion, platform, configHash
|
||||
setup_target_gobridge
|
||||
else
|
||||
setup_target_wireguardgo
|
||||
setup_target_wireguardtools
|
||||
end
|
||||
|
||||
setup_target_balrog if platform == 'macos'
|
||||
|
||||
@project.save
|
||||
end
|
||||
|
||||
def open_project(file)
|
||||
@project = Xcodeproj::Project.open(file)
|
||||
puts 'Failed to open the project file: ' + file if @project.nil?
|
||||
die 'Failed to open the project file: ' + file if @project.nil?
|
||||
end
|
||||
|
||||
def open_target_main
|
||||
@target_main = @project.targets.find { |target| target.to_s == 'AmneziaVPN' }
|
||||
return @target_main if not @target_main.nil?
|
||||
|
||||
puts 'Unable to open AmneziaVPN target'
|
||||
die 'Unable to open AmneziaVPN target'
|
||||
end
|
||||
|
||||
def setup_target_main(shortVersion, fullVersion, platform, networkExtension, configHash, adjust_sdk_token)
|
||||
@target_main.build_configurations.each do |config|
|
||||
config.base_configuration_reference = @configFile
|
||||
|
||||
config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"'
|
||||
config.build_settings['SWIFT_VERSION'] ||= '5.0'
|
||||
config.build_settings['CLANG_ENABLE_MODULES'] ||= 'YES'
|
||||
config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] ||= p_root + "/" + 'macos/app/WireGuard-Bridging-Header.h'
|
||||
config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= [
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/3rd"
|
||||
]
|
||||
|
||||
# Versions and names
|
||||
config.build_settings['MARKETING_VERSION'] ||= shortVersion
|
||||
config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion
|
||||
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = configHash['APP_ID_MACOS'] if platform == 'macos'
|
||||
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = configHash['APP_ID_IOS'] if platform == 'ios'
|
||||
config.build_settings['PRODUCT_NAME'] = 'Mozilla VPN'
|
||||
|
||||
# other config
|
||||
config.build_settings['INFOPLIST_FILE'] ||= p_root + "/" + platform + '/app/Info.plist'
|
||||
if platform == 'ios'
|
||||
config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'ios/app/main.entitlements'
|
||||
if adjust_sdk_token != ""
|
||||
config.build_settings['ADJUST_SDK_TOKEN'] = adjust_sdk_token
|
||||
end
|
||||
elsif networkExtension
|
||||
config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'macos/app/app.entitlements'
|
||||
else
|
||||
config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'macos/app/daemon.entitlements'
|
||||
end
|
||||
|
||||
config.build_settings['CODE_SIGN_IDENTITY'] ||= 'Apple Development'
|
||||
config.build_settings['ENABLE_BITCODE'] ||= 'NO' if platform == 'ios'
|
||||
config.build_settings['SDKROOT'] = 'iphoneos' if platform == 'ios'
|
||||
config.build_settings['SWIFT_PRECOMPILE_BRIDGING_HEADER'] = 'NO' if platform == 'ios'
|
||||
|
||||
groupId = "";
|
||||
if (platform == 'macos')
|
||||
groupId = configHash['DEVELOPMENT_TEAM'] + "." + configHash['GROUP_ID_MACOS']
|
||||
config.build_settings['APP_ID_MACOS'] ||= configHash['APP_ID_MACOS']
|
||||
else
|
||||
groupId = configHash['GROUP_ID_IOS']
|
||||
config.build_settings['GROUP_ID_IOS'] ||= configHash['GROUP_ID_IOS']
|
||||
end
|
||||
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||
'GROUP_ID=\"' + groupId + '\"',
|
||||
"VPN_NE_BUNDLEID=\\\"" + (platform == 'macos' ? configHash['NETEXT_ID_MACOS'] : configHash['NETEXT_ID_IOS']) + "\\\"",
|
||||
]
|
||||
|
||||
if config.name == 'Release'
|
||||
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone'
|
||||
end
|
||||
end
|
||||
|
||||
if networkExtension
|
||||
# WireGuard group
|
||||
group = @project.main_group.new_group('WireGuard')
|
||||
|
||||
[
|
||||
'macos/gobridge/wireguard-go-version.h',
|
||||
'3rd/wireguard-apple/Sources/Shared/Keychain.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardApp/LocalizationHelper.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift',
|
||||
].each { |filename|
|
||||
file = group.new_file(p_root + "/" + filename)
|
||||
@target_main.add_file_references([file])
|
||||
}
|
||||
|
||||
# @target_main + swift integration
|
||||
group = @project.main_group.new_group('SwiftIntegration')
|
||||
|
||||
[
|
||||
'platforms/ios/ioscontroller.swift',
|
||||
'platforms/ios/ioslogger.swift',
|
||||
].each { |filename|
|
||||
file = group.new_file(p_root + "/" + filename)
|
||||
@target_main.add_file_references([file])
|
||||
}
|
||||
end
|
||||
|
||||
if (platform == 'ios' && adjust_sdk_token != "")
|
||||
frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' }
|
||||
frameworks_build_phase = @target_main.build_phases.find { |build_phase| build_phase.to_s == 'FrameworksBuildPhase' }
|
||||
embed_frameworks_build_phase = @target_main.build_phases.find { |build_phase| build_phase.to_s == 'Embed Frameworks' }
|
||||
|
||||
framework_ref = frameworks_group.new_file('3rd/AdjustSdk.framework')
|
||||
frameworks_build_phase.add_file_reference(framework_ref)
|
||||
|
||||
framework_file = embed_frameworks_build_phase.add_file_reference(framework_ref)
|
||||
framework_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy', 'CodeSignOnCopy'] }
|
||||
|
||||
framework_ref = frameworks_group.new_file('AdServices.framework')
|
||||
frameworks_build_phase.add_file_reference(framework_ref)
|
||||
|
||||
framework_ref = frameworks_group.new_file('iAd.framework')
|
||||
frameworks_build_phase.add_file_reference(framework_ref)
|
||||
end
|
||||
end
|
||||
|
||||
def setup_target_extension(shortVersion, fullVersion, platform, configHash)
|
||||
@target_extension = @project.new_target(:app_extension, 'WireGuardNetworkExtension', platform == 'macos' ? :osx : :ios)
|
||||
|
||||
@target_extension.build_configurations.each do |config|
|
||||
config.base_configuration_reference = @configFile
|
||||
|
||||
config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"'
|
||||
config.build_settings['SWIFT_VERSION'] ||= '5.0'
|
||||
config.build_settings['CLANG_ENABLE_MODULES'] ||= 'YES'
|
||||
config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] ||= p_root + "/" + 'macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h'
|
||||
config.build_settings['SWIFT_PRECOMPILE_BRIDGING_HEADER'] = 'NO'
|
||||
|
||||
# Versions and names
|
||||
config.build_settings['MARKETING_VERSION'] ||= shortVersion
|
||||
config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion
|
||||
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NETEXT_ID_MACOS'] if platform == 'macos'
|
||||
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NETEXT_ID_IOS'] if platform == 'ios'
|
||||
config.build_settings['PRODUCT_NAME'] = 'WireGuardNetworkExtension'
|
||||
|
||||
# other configs
|
||||
config.build_settings['INFOPLIST_FILE'] ||= p_root + "/" + 'macos/networkextension/Info.plist'
|
||||
config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + platform + '/networkextension/AmneziaVPNNetworkExtension.entitlements'
|
||||
config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Development'
|
||||
|
||||
if platform == 'ios'
|
||||
config.build_settings['ENABLE_BITCODE'] ||= 'NO'
|
||||
config.build_settings['SDKROOT'] = 'iphoneos'
|
||||
|
||||
config.build_settings['OTHER_LDFLAGS'] ||= [
|
||||
"-stdlib=libc++",
|
||||
"-Wl,-rpath,@executable_path/Frameworks",
|
||||
"-framework",
|
||||
"AssetsLibrary",
|
||||
"-framework",
|
||||
"MobileCoreServices",
|
||||
"-lm",
|
||||
"-framework",
|
||||
"UIKit",
|
||||
"-lz",
|
||||
"-framework",
|
||||
"OpenGLES",
|
||||
]
|
||||
end
|
||||
|
||||
groupId = "";
|
||||
if (platform == 'macos')
|
||||
groupId = configHash['DEVELOPMENT_TEAM'] + "." + configHash['GROUP_ID_MACOS']
|
||||
else
|
||||
groupId = configHash['GROUP_ID_IOS']
|
||||
end
|
||||
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||
# This is needed to compile the iosglue without Qt.
|
||||
'NETWORK_EXTENSION=1',
|
||||
'GROUP_ID=\"' + groupId + '\"',
|
||||
]
|
||||
|
||||
if config.name == 'Release'
|
||||
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
group = @project.main_group.new_group('WireGuardExtension')
|
||||
[
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/WireGuardAdapter.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/PacketTunnelSettingsGenerator.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/DNSResolver.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardNetworkExtension/ErrorNotifier.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/Keychain.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/Model/TunnelConfiguration+WgQuickConfig.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/Model/NETunnelProviderProtocol+Extension.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/Model/String+ArrayConversion.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/TunnelConfiguration.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/IPAddressRange.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/Endpoint.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/DNSServer.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/InterfaceConfiguration.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/PeerConfiguration.swift',
|
||||
'3rd/wireguard-apple/Sources/Shared/FileManager+Extension.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKitC/x25519.c',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/Array+ConcurrentMap.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/IPAddress+AddrInfo.swift',
|
||||
'3rd/wireguard-apple/Sources/WireGuardKit/PrivateKey.swift',
|
||||
].each { |filename|
|
||||
file = group.new_file(p_root + "/" + filename)
|
||||
@target_extension.add_file_references([file])
|
||||
}
|
||||
# @target_extension + swift integration
|
||||
group = @project.main_group.new_group('SwiftIntegration')
|
||||
|
||||
[
|
||||
'platforms/ios/iostunnel.swift',
|
||||
'platforms/ios/iosglue.mm',
|
||||
'platforms/ios/ioslogger.swift',
|
||||
].each { |filename|
|
||||
file = group.new_file(p_root + "/" + filename)
|
||||
@target_extension.add_file_references([file])
|
||||
}
|
||||
|
||||
frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' }
|
||||
frameworks_build_phase = @target_extension.build_phases.find { |build_phase| build_phase.to_s == 'FrameworksBuildPhase' }
|
||||
|
||||
frameworks_build_phase.clear
|
||||
|
||||
framework_ref = frameworks_group.new_file('libwg-go.a')
|
||||
frameworks_build_phase.add_file_reference(framework_ref)
|
||||
|
||||
framework_ref = frameworks_group.new_file('NetworkExtension.framework')
|
||||
frameworks_build_phase.add_file_reference(framework_ref)
|
||||
|
||||
# This fails: @target_main.add_dependency @target_extension
|
||||
container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy)
|
||||
container_proxy.container_portal = @project.root_object.uuid
|
||||
container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target]
|
||||
container_proxy.remote_global_id_string = @target_extension.uuid
|
||||
container_proxy.remote_info = @target_extension.name
|
||||
|
||||
dependency = @project.new(Xcodeproj::Project::PBXTargetDependency)
|
||||
dependency.name = @target_extension.name
|
||||
dependency.target = @target_main
|
||||
dependency.target_proxy = container_proxy
|
||||
|
||||
@target_main.dependencies << dependency
|
||||
|
||||
copy_appex = @target_main.new_copy_files_build_phase
|
||||
copy_appex.name = 'Copy Network-Extension plugin'
|
||||
copy_appex.symbol_dst_subfolder_spec = :plug_ins
|
||||
|
||||
appex_file = copy_appex.add_file_reference @target_extension.product_reference
|
||||
appex_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] }
|
||||
end
|
||||
|
||||
def setup_target_gobridge
|
||||
target_gobridge = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget)
|
||||
|
||||
target_gobridge.build_working_directory = p_root + "/" + 'macos/gobridge'
|
||||
target_gobridge.build_tool_path = 'make'
|
||||
target_gobridge.pass_build_settings_in_environment = '1'
|
||||
target_gobridge.build_arguments_string = '$(ACTION)'
|
||||
target_gobridge.name = 'WireGuardGoBridge'
|
||||
target_gobridge.product_name = 'WireGuardGoBridge'
|
||||
|
||||
@project.targets << target_gobridge
|
||||
@target_extension.add_dependency target_gobridge
|
||||
end
|
||||
|
||||
def setup_target_balrog
|
||||
target_balrog = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget)
|
||||
|
||||
target_balrog.build_working_directory = p_root + "/" + 'balrog'
|
||||
target_balrog.build_tool_path = 'make'
|
||||
target_balrog.pass_build_settings_in_environment = '1'
|
||||
target_balrog.build_arguments_string = '$(ACTION)'
|
||||
target_balrog.name = 'WireGuardBalrog'
|
||||
target_balrog.product_name = 'WireGuardBalrog'
|
||||
|
||||
@project.targets << target_balrog
|
||||
|
||||
frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' }
|
||||
frameworks_build_phase = @target_main.build_phases.find { |build_phase| build_phase.to_s == 'FrameworksBuildPhase' }
|
||||
|
||||
framework_ref = frameworks_group.new_file('balrog/balrog.a')
|
||||
frameworks_build_phase.add_file_reference(framework_ref)
|
||||
|
||||
# This fails: @target_main.add_dependency target_balrog
|
||||
container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy)
|
||||
container_proxy.container_portal = @project.root_object.uuid
|
||||
container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target]
|
||||
container_proxy.remote_global_id_string = target_balrog.uuid
|
||||
container_proxy.remote_info = target_balrog.name
|
||||
|
||||
dependency = @project.new(Xcodeproj::Project::PBXTargetDependency)
|
||||
dependency.name = target_balrog.name
|
||||
dependency.target = @target_main
|
||||
dependency.target_proxy = container_proxy
|
||||
|
||||
@target_main.dependencies << dependency
|
||||
end
|
||||
|
||||
def setup_target_wireguardtools
|
||||
target_wireguardtools = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget)
|
||||
|
||||
target_wireguardtools.build_working_directory = p_root + "/" + '3rd/wireguard-tools/src'
|
||||
target_wireguardtools.build_tool_path = 'make'
|
||||
target_wireguardtools.pass_build_settings_in_environment = '1'
|
||||
target_wireguardtools.build_arguments_string = '$(ACTION)'
|
||||
target_wireguardtools.name = 'WireGuardTools'
|
||||
target_wireguardtools.product_name = 'WireGuardTools'
|
||||
|
||||
@project.targets << target_wireguardtools
|
||||
|
||||
# This fails: @target_main.add_dependency target_wireguardtools
|
||||
container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy)
|
||||
container_proxy.container_portal = @project.root_object.uuid
|
||||
container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target]
|
||||
container_proxy.remote_global_id_string = target_wireguardtools.uuid
|
||||
container_proxy.remote_info = target_wireguardtools.name
|
||||
|
||||
dependency = @project.new(Xcodeproj::Project::PBXTargetDependency)
|
||||
dependency.name = target_wireguardtools.name
|
||||
dependency.target = @target_main
|
||||
dependency.target_proxy = container_proxy
|
||||
|
||||
@target_main.dependencies << dependency
|
||||
|
||||
copy_wireguardTools = @target_main.new_copy_files_build_phase
|
||||
copy_wireguardTools.name = 'Copy wireguard-tools'
|
||||
copy_wireguardTools.symbol_dst_subfolder_spec = :wrapper
|
||||
copy_wireguardTools.dst_path = 'Contents/Resources/utils'
|
||||
|
||||
group = @project.main_group.new_group('WireGuardTools')
|
||||
file = group.new_file '3rd/wireguard-tools/src/wg'
|
||||
|
||||
wireguardTools_file = copy_wireguardTools.add_file_reference file
|
||||
wireguardTools_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] }
|
||||
end
|
||||
|
||||
def setup_target_wireguardgo
|
||||
target_wireguardgo = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget)
|
||||
|
||||
target_wireguardgo.build_working_directory = p_root + "/" + '3rd/wireguard-go'
|
||||
target_wireguardgo.build_tool_path = 'make'
|
||||
target_wireguardgo.pass_build_settings_in_environment = '1'
|
||||
target_wireguardgo.build_arguments_string = '$(ACTION)'
|
||||
target_wireguardgo.name = 'WireGuardGo'
|
||||
target_wireguardgo.product_name = 'WireGuardGo'
|
||||
|
||||
@project.targets << target_wireguardgo
|
||||
|
||||
# This fails: @target_main.add_dependency target_wireguardgo
|
||||
container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy)
|
||||
container_proxy.container_portal = @project.root_object.uuid
|
||||
container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target]
|
||||
container_proxy.remote_global_id_string = target_wireguardgo.uuid
|
||||
container_proxy.remote_info = target_wireguardgo.name
|
||||
|
||||
dependency = @project.new(Xcodeproj::Project::PBXTargetDependency)
|
||||
dependency.name = target_wireguardgo.name
|
||||
dependency.target = @target_main
|
||||
dependency.target_proxy = container_proxy
|
||||
|
||||
@target_main.dependencies << dependency
|
||||
|
||||
copy_wireguardGo = @target_main.new_copy_files_build_phase
|
||||
copy_wireguardGo.name = 'Copy wireguard-go'
|
||||
copy_wireguardGo.symbol_dst_subfolder_spec = :wrapper
|
||||
copy_wireguardGo.dst_path = 'Contents/Resources/utils'
|
||||
|
||||
group = @project.main_group.new_group('WireGuardGo')
|
||||
file = group.new_file '3rd/wireguard-go/wireguard-go'
|
||||
|
||||
wireguardGo_file = copy_wireguardGo.add_file_reference file
|
||||
wireguardGo_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] }
|
||||
end
|
||||
|
||||
def setup_target_loginitem(shortVersion, fullVersion, configHash)
|
||||
return
|
||||
@target_loginitem = @project.new_target(:application, 'AmneziaVPNLoginItem', :osx)
|
||||
|
||||
@target_loginitem.build_configurations.each do |config|
|
||||
config.base_configuration_reference = @configFile
|
||||
|
||||
config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"'
|
||||
|
||||
# Versions and names
|
||||
config.build_settings['MARKETING_VERSION'] ||= shortVersion
|
||||
config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion
|
||||
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['LOGIN_ID_MACOS']
|
||||
config.build_settings['PRODUCT_NAME'] = 'AmneziaVPNLoginItem'
|
||||
|
||||
# other configs
|
||||
config.build_settings['INFOPLIST_FILE'] ||= 'macos/loginitem/Info.plist'
|
||||
config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'macos/loginitem/AmneziaVPNLoginItem.entitlements'
|
||||
config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Development'
|
||||
config.build_settings['SKIP_INSTALL'] = 'YES'
|
||||
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||
'APP_ID=\"' + configHash['APP_ID_MACOS'] + '\"',
|
||||
]
|
||||
|
||||
if config.name == 'Release'
|
||||
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone'
|
||||
end
|
||||
end
|
||||
|
||||
group = @project.main_group.new_group('LoginItem')
|
||||
[
|
||||
'macos/loginitem/main.m',
|
||||
].each { |filename|
|
||||
file = group.new_file(filename)
|
||||
@target_loginitem.add_file_references([file])
|
||||
}
|
||||
|
||||
# This fails: @target_main.add_dependency @target_loginitem
|
||||
container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy)
|
||||
container_proxy.container_portal = @project.root_object.uuid
|
||||
container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target]
|
||||
container_proxy.remote_global_id_string = @target_loginitem.uuid
|
||||
container_proxy.remote_info = @target_loginitem.name
|
||||
|
||||
dependency = @project.new(Xcodeproj::Project::PBXTargetDependency)
|
||||
dependency.name = @target_loginitem.name
|
||||
dependency.target = @target_main
|
||||
dependency.target_proxy = container_proxy
|
||||
|
||||
@target_main.dependencies << dependency
|
||||
|
||||
copy_app = @target_main.new_copy_files_build_phase
|
||||
copy_app.name = 'Copy LoginItem'
|
||||
copy_app.symbol_dst_subfolder_spec = :wrapper
|
||||
copy_app.dst_path = 'Contents/Library/LoginItems'
|
||||
|
||||
app_file = copy_app.add_file_reference @target_loginitem.product_reference
|
||||
app_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] }
|
||||
end
|
||||
|
||||
def setup_target_nativemessaging(shortVersion, fullVersion, configHash)
|
||||
@target_nativemessaging = @project.new_target(:application, 'AmneziaVPNNativeMessaging', :osx)
|
||||
|
||||
@target_nativemessaging.build_configurations.each do |config|
|
||||
config.base_configuration_reference = @configFile
|
||||
|
||||
config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"'
|
||||
|
||||
# Versions and names
|
||||
config.build_settings['MARKETING_VERSION'] ||= shortVersion
|
||||
config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion
|
||||
config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NATIVEMESSAGING_ID_MACOS']
|
||||
config.build_settings['PRODUCT_NAME'] = 'AmneziaVPNNativeMessaging'
|
||||
|
||||
# other configs
|
||||
config.build_settings['INFOPLIST_FILE'] ||= p_root + "/" + 'macos/nativeMessaging/Info.plist'
|
||||
config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= p_root + "/" + 'macos/nativeMessaging/AmneziaVPNNativeMessaging.entitlements'
|
||||
config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Development'
|
||||
config.build_settings['SKIP_INSTALL'] = 'YES'
|
||||
end
|
||||
|
||||
group = @project.main_group.new_group('NativeMessaging')
|
||||
[
|
||||
'extension/app/constants.h',
|
||||
'extension/app/handler.cpp',
|
||||
'extension/app/handler.h',
|
||||
'extension/app/json.hpp',
|
||||
'extension/app/logger.cpp',
|
||||
'extension/app/logger.h',
|
||||
'extension/app/main.cpp',
|
||||
'extension/app/vpnconnection.cpp',
|
||||
'extension/app/vpnconnection.h',
|
||||
].each { |filename|
|
||||
file = group.new_file(p_root + "/" + filename)
|
||||
@target_nativemessaging.add_file_references([file])
|
||||
}
|
||||
|
||||
# This fails: @target_main.add_dependency @target_nativemessaging
|
||||
container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy)
|
||||
container_proxy.container_portal = @project.root_object.uuid
|
||||
container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target]
|
||||
container_proxy.remote_global_id_string = @target_nativemessaging.uuid
|
||||
container_proxy.remote_info = @target_nativemessaging.name
|
||||
|
||||
dependency = @project.new(Xcodeproj::Project::PBXTargetDependency)
|
||||
dependency.name = @target_nativemessaging.name
|
||||
dependency.target = @target_main
|
||||
dependency.target_proxy = container_proxy
|
||||
|
||||
@target_main.dependencies << dependency
|
||||
|
||||
copy_app = @target_main.new_copy_files_build_phase
|
||||
copy_app.name = 'Copy LoginItem'
|
||||
copy_app.symbol_dst_subfolder_spec = :wrapper
|
||||
copy_app.dst_path = 'Contents/Library/NativeMessaging'
|
||||
|
||||
app_file = copy_app.add_file_reference @target_nativemessaging.product_reference
|
||||
app_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] }
|
||||
|
||||
copy_nativeMessagingManifest = @target_main.new_copy_files_build_phase
|
||||
copy_nativeMessagingManifest.name = 'Copy native messaging manifest'
|
||||
copy_nativeMessagingManifest.symbol_dst_subfolder_spec = :wrapper
|
||||
copy_nativeMessagingManifest.dst_path = 'Contents/Resources/utils'
|
||||
|
||||
group = @project.main_group.new_group('WireGuardHelper')
|
||||
file = group.new_file 'extension/app/manifests/macos/mozillavpn.json'
|
||||
|
||||
nativeMessagingManifest_file = copy_nativeMessagingManifest.add_file_reference file
|
||||
nativeMessagingManifest_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] }
|
||||
end
|
||||
|
||||
def die(msg)
|
||||
print $msg
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
if ARGV.length < 4 || (ARGV[4] != "ios" && ARGV[4] != "macos")
|
||||
puts "Usage: <script> project_file shortVersion fullVersion ios/macos"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if !File.exist?("ios/xcode.xconfig")
|
||||
puts "xcode.xconfig file is required! See the template file."
|
||||
exit 1
|
||||
end
|
||||
|
||||
config = Hash.new
|
||||
configFile = File.read("ios/xcode.xconfig").split("\n")
|
||||
configFile.each { |line|
|
||||
next if line[0] == "#"
|
||||
|
||||
if line.include? "="
|
||||
keys = line.split("=")
|
||||
config[keys[0].strip] = keys[1].strip
|
||||
end
|
||||
}
|
||||
|
||||
platform = "macos"
|
||||
platform = "ios" if ARGV[4] == "ios"
|
||||
networkExtension = true
|
||||
webExtension = false
|
||||
adjustToken = ""
|
||||
|
||||
r = XCodeprojPatcher.new
|
||||
r.run ARGV[0], ARGV[1], ARGV[2], ARGV[3], platform, networkExtension, webExtension, config, adjustToken
|
||||
exit 0
|
59
client/logger.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* 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 "logger.h"
|
||||
#include "loghandler.h"
|
||||
|
||||
Logger::Logger(const QString& module, const QString& className)
|
||||
: Logger(QStringList({module}), className) {}
|
||||
|
||||
Logger::Logger(const QStringList& modules, const QString& className)
|
||||
: m_modules(modules), m_className(className) {}
|
||||
|
||||
Logger::Log Logger::error() { return Log(this, LogLevel::Error); }
|
||||
Logger::Log Logger::warning() { return Log(this, LogLevel::Warning); }
|
||||
Logger::Log Logger::info() { return Log(this, LogLevel::Info); }
|
||||
Logger::Log Logger::debug() { return Log(this, LogLevel::Debug); }
|
||||
|
||||
Logger::Log::Log(Logger* logger, LogLevel logLevel)
|
||||
: m_logger(logger), m_logLevel(logLevel), m_data(new Data()) {}
|
||||
|
||||
Logger::Log::~Log() {
|
||||
LogHandler::messageHandler(m_logLevel, m_logger->modules(),
|
||||
m_logger->className(), m_data->m_buffer.trimmed());
|
||||
delete m_data;
|
||||
}
|
||||
|
||||
#define CREATE_LOG_OP_REF(x) \
|
||||
Logger::Log& Logger::Log::operator<<(x t) { \
|
||||
m_data->m_ts << t << ' '; \
|
||||
return *this; \
|
||||
}
|
||||
|
||||
CREATE_LOG_OP_REF(uint64_t);
|
||||
CREATE_LOG_OP_REF(const char*);
|
||||
CREATE_LOG_OP_REF(const QString&);
|
||||
CREATE_LOG_OP_REF(const QByteArray&);
|
||||
CREATE_LOG_OP_REF(void*);
|
||||
|
||||
#undef CREATE_LOG_OP_REF
|
||||
|
||||
Logger::Log& Logger::Log::operator<<(const QStringList& t) {
|
||||
m_data->m_ts << '[' << t.join(",") << ']' << ' ';
|
||||
return *this;
|
||||
}
|
||||
|
||||
Logger::Log& Logger::Log::operator<<(QTextStreamFunction t) {
|
||||
m_data->m_ts << t;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// static
|
||||
QString Logger::sensitive(const QString& input) {
|
||||
#ifdef QT_DEBUG
|
||||
return input;
|
||||
#else
|
||||
return QString(input.length(), 'X');
|
||||
#endif
|
||||
}
|
88
client/logger.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
/* 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 LOGGER_H
|
||||
#define LOGGER_H
|
||||
|
||||
#include "loglevel.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QTextStream>
|
||||
|
||||
constexpr const char* LOG_CAPTIVEPORTAL = "captiveportal";
|
||||
constexpr const char* LOG_CONTROLLER = "controller";
|
||||
constexpr const char* LOG_IAP = "iap";
|
||||
constexpr const char* LOG_INSPECTOR = "inspector";
|
||||
constexpr const char* LOG_MAIN = "main";
|
||||
constexpr const char* LOG_MODEL = "model";
|
||||
constexpr const char* LOG_NETWORKING = "networking";
|
||||
constexpr const char* LOG_SERVER = "server";
|
||||
|
||||
#if defined(MVPN_LINUX) || defined(MVPN_ANDROID)
|
||||
constexpr const char* LOG_LINUX = "linux";
|
||||
#endif
|
||||
|
||||
#ifdef MVPN_WINDOWS
|
||||
constexpr const char* LOG_WINDOWS = "windows";
|
||||
#endif
|
||||
|
||||
#if __APPLE__ || defined(MVPN_WASM)
|
||||
constexpr const char* LOG_MACOS = "macos";
|
||||
constexpr const char* LOG_IOS = "ios";
|
||||
#endif
|
||||
|
||||
#if defined(MVPN_ANDROID) || defined(UNIT_TEST)
|
||||
constexpr const char* LOG_ANDROID = "android";
|
||||
#endif
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
Logger(const QString& module, const QString& className);
|
||||
Logger(const QStringList& modules, const QString& className);
|
||||
|
||||
const QStringList& modules() const { return m_modules; }
|
||||
const QString& className() const { return m_className; }
|
||||
|
||||
class Log {
|
||||
public:
|
||||
Log(Logger* logger, LogLevel level);
|
||||
~Log();
|
||||
|
||||
Log& operator<<(uint64_t t);
|
||||
Log& operator<<(const char* t);
|
||||
Log& operator<<(const QString& t);
|
||||
Log& operator<<(const QStringList& t);
|
||||
Log& operator<<(const QByteArray& t);
|
||||
Log& operator<<(QTextStreamFunction t);
|
||||
Log& operator<<(void* t);
|
||||
|
||||
private:
|
||||
Logger* m_logger;
|
||||
LogLevel m_logLevel;
|
||||
|
||||
struct Data {
|
||||
Data() : m_ts(&m_buffer, QIODevice::WriteOnly) {}
|
||||
|
||||
QString m_buffer;
|
||||
QTextStream m_ts;
|
||||
};
|
||||
|
||||
Data* m_data;
|
||||
};
|
||||
|
||||
Log error();
|
||||
Log warning();
|
||||
Log info();
|
||||
Log debug();
|
||||
|
||||
// Use this to log sensitive data such as IP address, session tokens, and so
|
||||
// on.
|
||||
QString sensitive(const QString& input);
|
||||
|
||||
private:
|
||||
QStringList m_modules;
|
||||
QString m_className;
|
||||
};
|
||||
|
||||
#endif // LOGGER_H
|
346
client/loghandler.cpp
Normal file
|
@ -0,0 +1,346 @@
|
|||
/* 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 "loghandler.h"
|
||||
#include "constants.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QDate>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageLogContext>
|
||||
#include <QMutexLocker>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
#include <QTextStream>
|
||||
|
||||
#ifdef MVPN_ANDROID
|
||||
# include <android/log.h>
|
||||
#endif
|
||||
|
||||
constexpr qint64 LOG_MAX_FILE_SIZE = 204800;
|
||||
constexpr const char* LOG_FILENAME = "mozillavpn.txt";
|
||||
|
||||
namespace {
|
||||
QMutex s_mutex;
|
||||
QString s_location =
|
||||
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
LogHandler* s_instance = nullptr;
|
||||
|
||||
LogLevel qtTypeToLogLevel(QtMsgType type) {
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
return Debug;
|
||||
case QtInfoMsg:
|
||||
return Info;
|
||||
case QtWarningMsg:
|
||||
return Warning;
|
||||
case QtCriticalMsg:
|
||||
[[fallthrough]];
|
||||
case QtFatalMsg:
|
||||
return Error;
|
||||
default:
|
||||
return Debug;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
LogHandler* LogHandler::instance() {
|
||||
QMutexLocker lock(&s_mutex);
|
||||
return maybeCreate(lock);
|
||||
}
|
||||
|
||||
// static
|
||||
void LogHandler::messageQTHandler(QtMsgType type,
|
||||
const QMessageLogContext& context,
|
||||
const QString& message) {
|
||||
QMutexLocker lock(&s_mutex);
|
||||
maybeCreate(lock)->addLog(Log(qtTypeToLogLevel(type), context.file,
|
||||
context.function, context.line, message),
|
||||
lock);
|
||||
}
|
||||
|
||||
// static
|
||||
void LogHandler::messageHandler(LogLevel logLevel, const QStringList& modules,
|
||||
const QString& className,
|
||||
const QString& message) {
|
||||
QMutexLocker lock(&s_mutex);
|
||||
maybeCreate(lock)->addLog(Log(logLevel, modules, className, message), lock);
|
||||
}
|
||||
|
||||
// static
|
||||
LogHandler* LogHandler::maybeCreate(const QMutexLocker& proofOfLock) {
|
||||
if (!s_instance) {
|
||||
LogLevel minLogLevel = Debug; // TODO: in prod, we should log >= warning
|
||||
QStringList modules;
|
||||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||
if (pe.contains("MOZVPN_LEVEL")) {
|
||||
QString level = pe.value("MOZVPN_LEVEL");
|
||||
if (level == "info")
|
||||
minLogLevel = Info;
|
||||
else if (level == "warning")
|
||||
minLogLevel = Warning;
|
||||
else if (level == "error")
|
||||
minLogLevel = Error;
|
||||
}
|
||||
|
||||
if (pe.contains("MOZVPN_LOG")) {
|
||||
QStringList parts = pe.value("MOZVPN_LOG").split(",");
|
||||
for (const QString& part : parts) {
|
||||
modules.append(part.trimmed());
|
||||
}
|
||||
}
|
||||
|
||||
s_instance = new LogHandler(minLogLevel, modules, proofOfLock);
|
||||
}
|
||||
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
// static
|
||||
void LogHandler::prettyOutput(QTextStream& out, const LogHandler::Log& log) {
|
||||
out << "[" << log.m_dateTime.toString("dd.MM.yyyy hh:mm:ss.zzz") << "] ";
|
||||
|
||||
switch (log.m_logLevel) {
|
||||
case Debug:
|
||||
out << "Debug: ";
|
||||
break;
|
||||
case Info:
|
||||
out << "Info: ";
|
||||
break;
|
||||
case Warning:
|
||||
out << "Warning: ";
|
||||
break;
|
||||
case Error:
|
||||
out << "Error: ";
|
||||
break;
|
||||
default:
|
||||
out << "?!?: ";
|
||||
break;
|
||||
}
|
||||
|
||||
if (log.m_fromQT) {
|
||||
out << log.m_message;
|
||||
|
||||
if (!log.m_file.isEmpty() || !log.m_function.isEmpty()) {
|
||||
out << " (";
|
||||
|
||||
if (!log.m_file.isEmpty()) {
|
||||
int pos = log.m_file.lastIndexOf("/");
|
||||
out << log.m_file.right(log.m_file.length() - pos - 1);
|
||||
|
||||
if (log.m_line >= 0) {
|
||||
out << ":" << log.m_line;
|
||||
}
|
||||
|
||||
if (!log.m_function.isEmpty()) {
|
||||
out << ", ";
|
||||
}
|
||||
}
|
||||
|
||||
if (!log.m_function.isEmpty()) {
|
||||
out << log.m_function;
|
||||
}
|
||||
|
||||
out << ")";
|
||||
}
|
||||
} else {
|
||||
out << "(" << log.m_modules.join("|") << " - " << log.m_className << ") "
|
||||
<< log.m_message;
|
||||
}
|
||||
|
||||
out << Qt::endl;
|
||||
}
|
||||
|
||||
// static
|
||||
void LogHandler::enableDebug() {
|
||||
QMutexLocker lock(&s_mutex);
|
||||
maybeCreate(lock)->m_showDebug = true;
|
||||
}
|
||||
|
||||
LogHandler::LogHandler(LogLevel minLogLevel, const QStringList& modules,
|
||||
const QMutexLocker& proofOfLock)
|
||||
: m_minLogLevel(minLogLevel), m_modules(modules) {
|
||||
Q_UNUSED(proofOfLock);
|
||||
|
||||
#if defined(QT_DEBUG) || defined(MVPN_WASM)
|
||||
m_showDebug = true;
|
||||
#endif
|
||||
|
||||
if (!s_location.isEmpty()) {
|
||||
openLogFile(proofOfLock);
|
||||
}
|
||||
}
|
||||
|
||||
void LogHandler::addLog(const Log& log, const QMutexLocker& proofOfLock) {
|
||||
if (!matchLogLevel(log, proofOfLock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!matchModule(log, proofOfLock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_output) {
|
||||
prettyOutput(*m_output, log);
|
||||
}
|
||||
|
||||
if ((log.m_logLevel != LogLevel::Debug) || m_showDebug) {
|
||||
QTextStream out(stderr);
|
||||
prettyOutput(out, log);
|
||||
}
|
||||
|
||||
QByteArray buffer;
|
||||
{
|
||||
QTextStream out(&buffer);
|
||||
prettyOutput(out, log);
|
||||
}
|
||||
|
||||
emit logEntryAdded(buffer);
|
||||
|
||||
#if defined(MVPN_ANDROID) && defined(QT_DEBUG)
|
||||
const char* str = buffer.constData();
|
||||
if (str) {
|
||||
__android_log_write(ANDROID_LOG_DEBUG, "mozillavpn", str);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LogHandler::matchModule(const Log& log,
|
||||
const QMutexLocker& proofOfLock) const {
|
||||
Q_UNUSED(proofOfLock);
|
||||
|
||||
// Let's include QT logs always.
|
||||
if (log.m_fromQT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no modules has been specified, let's include all.
|
||||
if (m_modules.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const QString& module : log.m_modules) {
|
||||
if (m_modules.contains(module)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LogHandler::matchLogLevel(const Log& log,
|
||||
const QMutexLocker& proofOfLock) const {
|
||||
Q_UNUSED(proofOfLock);
|
||||
return log.m_logLevel >= m_minLogLevel;
|
||||
}
|
||||
|
||||
// static
|
||||
void LogHandler::writeLogs(QTextStream& out) {
|
||||
QMutexLocker lock(&s_mutex);
|
||||
|
||||
if (!s_instance || !s_instance->m_logFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString logFileName = s_instance->m_logFile->fileName();
|
||||
s_instance->closeLogFile(lock);
|
||||
|
||||
{
|
||||
QFile file(logFileName);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
out << file.readAll();
|
||||
}
|
||||
|
||||
s_instance->openLogFile(lock);
|
||||
}
|
||||
|
||||
// static
|
||||
void LogHandler::cleanupLogs() {
|
||||
QMutexLocker lock(&s_mutex);
|
||||
cleanupLogFile(lock);
|
||||
}
|
||||
|
||||
// static
|
||||
void LogHandler::cleanupLogFile(const QMutexLocker& proofOfLock) {
|
||||
if (!s_instance || !s_instance->m_logFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString logFileName = s_instance->m_logFile->fileName();
|
||||
s_instance->closeLogFile(proofOfLock);
|
||||
|
||||
{
|
||||
QFile file(logFileName);
|
||||
file.remove();
|
||||
}
|
||||
|
||||
s_instance->openLogFile(proofOfLock);
|
||||
}
|
||||
|
||||
// static
|
||||
void LogHandler::setLocation(const QString& path) {
|
||||
QMutexLocker lock(&s_mutex);
|
||||
s_location = path;
|
||||
|
||||
if (s_instance && s_instance->m_logFile) {
|
||||
cleanupLogFile(lock);
|
||||
}
|
||||
}
|
||||
|
||||
void LogHandler::openLogFile(const QMutexLocker& proofOfLock) {
|
||||
Q_UNUSED(proofOfLock);
|
||||
Q_ASSERT(!m_logFile);
|
||||
Q_ASSERT(!m_output);
|
||||
|
||||
QDir appDataLocation(s_location);
|
||||
if (!appDataLocation.exists()) {
|
||||
QDir tmp(s_location);
|
||||
tmp.cdUp();
|
||||
if (!tmp.exists()) {
|
||||
return;
|
||||
}
|
||||
if (!tmp.mkdir(appDataLocation.dirName())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QString logFileName = appDataLocation.filePath(LOG_FILENAME);
|
||||
m_logFile = new QFile(logFileName);
|
||||
if (m_logFile->size() > LOG_MAX_FILE_SIZE) {
|
||||
m_logFile->remove();
|
||||
}
|
||||
|
||||
if (!m_logFile->open(QIODevice::WriteOnly | QIODevice::Append |
|
||||
QIODevice::Text)) {
|
||||
delete m_logFile;
|
||||
m_logFile = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
m_output = new QTextStream(m_logFile);
|
||||
|
||||
addLog(Log(Debug, QStringList{LOG_MAIN}, "LogHandler",
|
||||
QString("Log file: %1").arg(logFileName)),
|
||||
proofOfLock);
|
||||
}
|
||||
|
||||
void LogHandler::closeLogFile(const QMutexLocker& proofOfLock) {
|
||||
Q_UNUSED(proofOfLock);
|
||||
|
||||
if (m_logFile) {
|
||||
delete m_output;
|
||||
m_output = nullptr;
|
||||
|
||||
delete m_logFile;
|
||||
m_logFile = nullptr;
|
||||
}
|
||||
}
|
102
client/loghandler.h
Normal file
|
@ -0,0 +1,102 @@
|
|||
/* 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 LOGHANDLER_H
|
||||
#define LOGHANDLER_H
|
||||
|
||||
#include "loglevel.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
class QFile;
|
||||
class QMutexLocker;
|
||||
class QTextStream;
|
||||
|
||||
class LogHandler final : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Log {
|
||||
Log() = default;
|
||||
|
||||
Log(LogLevel logLevel, const QStringList& modules, const QString& className,
|
||||
const QString& message)
|
||||
: m_logLevel(logLevel),
|
||||
m_dateTime(QDateTime::currentDateTime()),
|
||||
m_modules(modules),
|
||||
m_className(className),
|
||||
m_message(message),
|
||||
m_fromQT(false) {}
|
||||
|
||||
Log(LogLevel logLevel, const QString& file, const QString& function,
|
||||
uint32_t line, const QString& message)
|
||||
: m_logLevel(logLevel),
|
||||
m_dateTime(QDateTime::currentDateTime()),
|
||||
m_file(file),
|
||||
m_function(function),
|
||||
m_message(message),
|
||||
m_line(line),
|
||||
m_fromQT(true) {}
|
||||
|
||||
LogLevel m_logLevel = LogLevel::Debug;
|
||||
QDateTime m_dateTime;
|
||||
QString m_file;
|
||||
QString m_function;
|
||||
QStringList m_modules;
|
||||
QString m_className;
|
||||
QString m_message;
|
||||
int32_t m_line = -1;
|
||||
bool m_fromQT = false;
|
||||
};
|
||||
|
||||
static LogHandler* instance();
|
||||
|
||||
static void messageQTHandler(QtMsgType type,
|
||||
const QMessageLogContext& context,
|
||||
const QString& message);
|
||||
|
||||
static void messageHandler(LogLevel logLevel, const QStringList& modules,
|
||||
const QString& className, const QString& message);
|
||||
|
||||
static void prettyOutput(QTextStream& out, const LogHandler::Log& log);
|
||||
|
||||
static void writeLogs(QTextStream& out);
|
||||
|
||||
static void cleanupLogs();
|
||||
|
||||
static void setLocation(const QString& path);
|
||||
|
||||
static void enableDebug();
|
||||
|
||||
signals:
|
||||
void logEntryAdded(const QByteArray& log);
|
||||
|
||||
private:
|
||||
LogHandler(LogLevel m_minLogLevel, const QStringList& modules,
|
||||
const QMutexLocker& proofOfLock);
|
||||
|
||||
static LogHandler* maybeCreate(const QMutexLocker& proofOfLock);
|
||||
|
||||
void addLog(const Log& log, const QMutexLocker& proofOfLock);
|
||||
|
||||
bool matchLogLevel(const Log& log, const QMutexLocker& proofOfLock) const;
|
||||
bool matchModule(const Log& log, const QMutexLocker& proofOfLock) const;
|
||||
|
||||
void openLogFile(const QMutexLocker& proofOfLock);
|
||||
|
||||
void closeLogFile(const QMutexLocker& proofOfLock);
|
||||
|
||||
static void cleanupLogFile(const QMutexLocker& proofOfLock);
|
||||
|
||||
const LogLevel m_minLogLevel;
|
||||
const QStringList m_modules;
|
||||
bool m_showDebug = false;
|
||||
|
||||
QFile* m_logFile = nullptr;
|
||||
QTextStream* m_output = nullptr;
|
||||
};
|
||||
|
||||
#endif // LOGHANDLER_H
|
15
client/loglevel.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
/* 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 LOGLEVEL_H
|
||||
#define LOGLEVEL_H
|
||||
|
||||
enum LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
};
|
||||
|
||||
#endif // LOGLEVEL_H
|
BIN
client/macos/app/Images-beta.xcassets/AppIcon.appiconset/128.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 5.2 KiB |
BIN
client/macos/app/Images-beta.xcassets/AppIcon.appiconset/16.png
Normal file
After Width: | Height: | Size: 361 B |
After Width: | Height: | Size: 623 B |
BIN
client/macos/app/Images-beta.xcassets/AppIcon.appiconset/256.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 12 KiB |
BIN
client/macos/app/Images-beta.xcassets/AppIcon.appiconset/32.png
Normal file
After Width: | Height: | Size: 623 B |
After Width: | Height: | Size: 1.1 KiB |
BIN
client/macos/app/Images-beta.xcassets/AppIcon.appiconset/512.png
Normal file
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 26 KiB |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/128.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/128@2x.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/16.png
Normal file
After Width: | Height: | Size: 336 B |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/16@2x.png
Normal file
After Width: | Height: | Size: 593 B |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/256.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/256@2x.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/32.png
Normal file
After Width: | Height: | Size: 593 B |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/32@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/512.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
client/macos/app/Images.xcassets/AppIcon.appiconset/512@2x.png
Normal file
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "16@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"filename" : "32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "32@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"filename" : "128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "128@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"filename" : "256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "256@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"filename" : "512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"filename" : "512@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
6
client/macos/app/Images.xcassets/Contents.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
50
client/macos/app/Info.plist
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
||||
|
||||
<key>LSMultipleInstancesProhibited</key>
|
||||
<true/>
|
||||
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
|
||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
29
client/macos/app/WireGuard-Bridging-Header.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "wireguard-go-version.h"
|
||||
#include "3rd/wireguard-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
|
35
client/macos/app/app.entitlements
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?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>com.apple.application-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
|
||||
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).*</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM)</string>
|
||||
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
27
client/macos/app/daemon.entitlements
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?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>com.apple.application-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
|
||||
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).*</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM)</string>
|
||||
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
3
client/macos/gobridge/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.cache/
|
||||
.tmp/
|
||||
out/
|
225
client/macos/gobridge/api.go
Executable file
|
@ -0,0 +1,225 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2018-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
// #include <stdlib.h>
|
||||
// #include <sys/types.h>
|
||||
// static void callLogger(void *func, void *ctx, int level, const char *msg)
|
||||
// {
|
||||
// ((void(*)(void *, int, const char *))func)(ctx, level, msg);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
var loggerFunc unsafe.Pointer
|
||||
var loggerCtx unsafe.Pointer
|
||||
|
||||
type CLogger int
|
||||
|
||||
func cstring(s string) *C.char {
|
||||
b, err := unix.BytePtrFromString(s)
|
||||
if err != nil {
|
||||
b := [1]C.char{}
|
||||
return &b[0]
|
||||
}
|
||||
return (*C.char)(unsafe.Pointer(b))
|
||||
}
|
||||
|
||||
func (l CLogger) Printf(format string, args ...interface{}) {
|
||||
if uintptr(loggerFunc) == 0 {
|
||||
return
|
||||
}
|
||||
C.callLogger(loggerFunc, loggerCtx, C.int(l), cstring(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
type tunnelHandle struct {
|
||||
*device.Device
|
||||
*device.Logger
|
||||
}
|
||||
|
||||
var tunnelHandles = make(map[int32]tunnelHandle)
|
||||
|
||||
func init() {
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, unix.SIGUSR2)
|
||||
go func() {
|
||||
buf := make([]byte, os.Getpagesize())
|
||||
for {
|
||||
select {
|
||||
case <-signals:
|
||||
n := runtime.Stack(buf, true)
|
||||
buf[n] = 0
|
||||
if uintptr(loggerFunc) != 0 {
|
||||
C.callLogger(loggerFunc, loggerCtx, 0, (*C.char)(unsafe.Pointer(&buf[0])))
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
//export wgSetLogger
|
||||
func wgSetLogger(context, loggerFn uintptr) {
|
||||
loggerCtx = unsafe.Pointer(context)
|
||||
loggerFunc = unsafe.Pointer(loggerFn)
|
||||
}
|
||||
|
||||
//export wgTurnOn
|
||||
func wgTurnOn(settings *C.char, tunFd int32) int32 {
|
||||
logger := &device.Logger{
|
||||
Verbosef: CLogger(0).Printf,
|
||||
Errorf: CLogger(1).Printf,
|
||||
}
|
||||
dupTunFd, err := unix.Dup(int(tunFd))
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to dup tun fd: %v", err)
|
||||
return -1
|
||||
}
|
||||
|
||||
err = unix.SetNonblock(dupTunFd, true)
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to set tun fd as non blocking: %v", err)
|
||||
unix.Close(dupTunFd)
|
||||
return -1
|
||||
}
|
||||
tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(dupTunFd), "/dev/tun"), 0)
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to create new tun device from fd: %v", err)
|
||||
unix.Close(dupTunFd)
|
||||
return -1
|
||||
}
|
||||
logger.Verbosef("Attaching to interface")
|
||||
dev := device.NewDevice(tun, conn.NewStdNetBind(), logger)
|
||||
|
||||
err = dev.IpcSet(C.GoString(settings))
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to set IPC settings: %v", err)
|
||||
unix.Close(dupTunFd)
|
||||
dev.Close()
|
||||
return -1
|
||||
}
|
||||
|
||||
dev.Up()
|
||||
logger.Verbosef("Device started")
|
||||
|
||||
var i int32
|
||||
for i = 0; i < math.MaxInt32; i++ {
|
||||
if _, exists := tunnelHandles[i]; !exists {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == math.MaxInt32 {
|
||||
unix.Close(dupTunFd)
|
||||
dev.Close()
|
||||
return -1
|
||||
}
|
||||
tunnelHandles[i] = tunnelHandle{dev, logger}
|
||||
return i
|
||||
}
|
||||
|
||||
//export wgTurnOff
|
||||
func wgTurnOff(tunnelHandle int32) {
|
||||
dev, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(tunnelHandles, tunnelHandle)
|
||||
dev.Close()
|
||||
}
|
||||
|
||||
//export wgSetConfig
|
||||
func wgSetConfig(tunnelHandle int32, settings *C.char) int64 {
|
||||
dev, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
err := dev.IpcSet(C.GoString(settings))
|
||||
if err != nil {
|
||||
dev.Errorf("Unable to set IPC settings: %v", err)
|
||||
if ipcErr, ok := err.(*device.IPCError); ok {
|
||||
return ipcErr.ErrorCode()
|
||||
}
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//export wgGetConfig
|
||||
func wgGetConfig(tunnelHandle int32) *C.char {
|
||||
device, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
settings, err := device.IpcGet()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return C.CString(settings)
|
||||
}
|
||||
|
||||
//export wgBumpSockets
|
||||
func wgBumpSockets(tunnelHandle int32) {
|
||||
dev, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
err := dev.BindUpdate()
|
||||
if err == nil {
|
||||
dev.SendKeepalivesToPeersWithCurrentKeypair()
|
||||
return
|
||||
}
|
||||
dev.Errorf("Unable to update bind, try %d: %v", i+1, err)
|
||||
time.Sleep(time.Second / 2)
|
||||
}
|
||||
dev.Errorf("Gave up trying to update bind; tunnel is likely dysfunctional")
|
||||
}()
|
||||
}
|
||||
|
||||
//export wgDisableSomeRoamingForBrokenMobileSemantics
|
||||
func wgDisableSomeRoamingForBrokenMobileSemantics(tunnelHandle int32) {
|
||||
dev, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
dev.DisableSomeRoamingForBrokenMobileSemantics()
|
||||
}
|
||||
|
||||
//export wgVersion
|
||||
func wgVersion() *C.char {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return C.CString("unknown")
|
||||
}
|
||||
for _, dep := range info.Deps {
|
||||
if dep.Path == "golang.zx2c4.com/wireguard" {
|
||||
parts := strings.Split(dep.Version, "-")
|
||||
if len(parts) == 3 && len(parts[2]) == 12 {
|
||||
return C.CString(parts[2][:7])
|
||||
}
|
||||
return C.CString(dep.Version)
|
||||
}
|
||||
}
|
||||
return C.CString("unknown")
|
||||
}
|
||||
|
||||
func main() {}
|
1
client/macos/gobridge/dummy.c
Executable file
|
@ -0,0 +1 @@
|
|||
// Empty
|
8
client/macos/gobridge/go.mod
Executable file
|
@ -0,0 +1,8 @@
|
|||
module golang.zx2c4.com/wireguard/apple
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.0.0-20210308170721-88b6017d0656
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210307162820-f4695db51c39
|
||||
)
|
19
client/macos/gobridge/go.sum
Executable file
|
@ -0,0 +1,19 @@
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305215415-5cdee2b1b5a0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210308170721-88b6017d0656 h1:FuBaiPCiXkq4v+JY5JEGPU/HwEZwpVyDbu/KBz9fU+4=
|
||||
golang.org/x/sys v0.0.0-20210308170721-88b6017d0656/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210307162820-f4695db51c39 h1:yv331J9aB1fuvxzneUKsRnWyhwK+aj495rADUXSP7Uk=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210307162820-f4695db51c39/go.mod h1:ojGPy+9W6ZSM8anL+xC67fvh8zPQJwA6KpFOHyDWLX4=
|
61
client/macos/gobridge/goruntime-boottime-over-monotonic.diff
Executable file
|
@ -0,0 +1,61 @@
|
|||
From 516dc0c15ff1ab781e0677606b5be72919251b3e Mon Sep 17 00:00:00 2001
|
||||
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||
Date: Wed, 9 Dec 2020 14:07:06 +0100
|
||||
Subject: [PATCH] runtime: use libc_mach_continuous_time in nanotime on Darwin
|
||||
|
||||
This makes timers account for having expired while a computer was
|
||||
asleep, which is quite common on mobile devices. Note that
|
||||
continuous_time absolute_time, except that it takes into account
|
||||
time spent in suspend.
|
||||
|
||||
Fixes #24595
|
||||
|
||||
Change-Id: Ia3282e8bd86f95ad2b76427063e60a005563f4eb
|
||||
---
|
||||
src/runtime/sys_darwin.go | 2 +-
|
||||
src/runtime/sys_darwin_amd64.s | 2 +-
|
||||
src/runtime/sys_darwin_arm64.s | 2 +-
|
||||
3 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go
|
||||
index 4a3f2fc453..4a69403b32 100644
|
||||
--- a/src/runtime/sys_darwin.go
|
||||
+++ b/src/runtime/sys_darwin.go
|
||||
@@ -440,7 +440,7 @@ func setNonblock(fd int32) {
|
||||
//go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib"
|
||||
|
||||
//go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib"
|
||||
-//go:cgo_import_dynamic libc_mach_absolute_time mach_absolute_time "/usr/lib/libSystem.B.dylib"
|
||||
+//go:cgo_import_dynamic libc_mach_continuous_time mach_continuous_time "/usr/lib/libSystem.B.dylib"
|
||||
//go:cgo_import_dynamic libc_clock_gettime clock_gettime "/usr/lib/libSystem.B.dylib"
|
||||
//go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib"
|
||||
//go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib"
|
||||
diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s
|
||||
index 630fb5df64..4499c88802 100644
|
||||
--- a/src/runtime/sys_darwin_amd64.s
|
||||
+++ b/src/runtime/sys_darwin_amd64.s
|
||||
@@ -114,7 +114,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
|
||||
PUSHQ BP
|
||||
MOVQ SP, BP
|
||||
MOVQ DI, BX
|
||||
- CALL libc_mach_absolute_time(SB)
|
||||
+ CALL libc_mach_continuous_time(SB)
|
||||
MOVQ AX, 0(BX)
|
||||
MOVL timebase<>+machTimebaseInfo_numer(SB), SI
|
||||
MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read
|
||||
diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s
|
||||
index 96d2ed1076..f046545395 100644
|
||||
--- a/src/runtime/sys_darwin_arm64.s
|
||||
+++ b/src/runtime/sys_darwin_arm64.s
|
||||
@@ -143,7 +143,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
|
||||
|
||||
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40
|
||||
MOVD R0, R19
|
||||
- BL libc_mach_absolute_time(SB)
|
||||
+ BL libc_mach_continuous_time(SB)
|
||||
MOVD R0, 0(R19)
|
||||
MOVW timebase<>+machTimebaseInfo_numer(SB), R20
|
||||
MOVD $timebase<>+machTimebaseInfo_denom(SB), R21
|
||||
--
|
||||
2.30.1
|
||||
|
5
client/macos/gobridge/module.modulemap
Executable file
|
@ -0,0 +1,5 @@
|
|||
module WireGuardKitGo {
|
||||
umbrella header "wireguard.h"
|
||||
link "wg-go"
|
||||
export *
|
||||
}
|
1
client/macos/gobridge/wireguard-go-version.h
Executable file
|
@ -0,0 +1 @@
|
|||
#define WIREGUARD_GO_VERSION "0.0.0"
|
23
client/macos/gobridge/wireguard.h
Executable file
|
@ -0,0 +1,23 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0
|
||||
*
|
||||
* Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef WIREGUARD_H
|
||||
#define WIREGUARD_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef void (*logger_fn_t)(void* context, int level, const char* msg);
|
||||
extern void wgSetLogger(void* context, logger_fn_t logger_fn);
|
||||
extern int wgTurnOn(const char* settings, int32_t tun_fd);
|
||||
extern void wgTurnOff(int handle);
|
||||
extern int64_t wgSetConfig(int handle, const char* settings);
|
||||
extern char* wgGetConfig(int handle);
|
||||
extern void wgBumpSockets(int handle);
|
||||
extern void wgDisableSomeRoamingForBrokenMobileSemantics(int handle);
|
||||
extern const char* wgVersion();
|
||||
|
||||
#endif
|
|
@ -0,0 +1,10 @@
|
|||
<?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>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
30
client/macos/nativemessaging/Info.plist
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?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>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${PRODUCT_BUNDLE_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>LSBackgroundOnly</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,38 @@
|
|||
<?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>com.apple.application-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
|
||||
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).*</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM)</string>
|
||||
|
||||
<key>com.apple.developer.system-extension.install</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
52
client/macos/networkextension/Info.plist
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?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>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.networkextension.packet-tunnel</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||
</dict>
|
||||
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
<string>group.$(APP_ID_IOS)</string>
|
||||
|
||||
<key>com.wireguard.macos.app_group_id</key>
|
||||
<string>$(DEVELOPMENT_TEAM).group.$(APP_ID_MACOS)</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macos/gobridge/wireguard.h"
|
||||
#include "wireguard-go-version.h"
|
||||
#include "3rd/wireguard-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);
|
16
client/platforms/ios/iosadjusthelper.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IOSADJUSTHELPER_H
|
||||
#define IOSADJUSTHELPER_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class IOSAdjustHelper final {
|
||||
public:
|
||||
static void initialize();
|
||||
static void trackEvent(const QString& eventToken);
|
||||
};
|
||||
|
||||
#endif // IOSADJUSTHELPER_H
|
37
client/platforms/ios/iosadjusthelper.mm
Normal file
|
@ -0,0 +1,37 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "iosadjusthelper.h"
|
||||
#include "logger.h"
|
||||
#include "constants.h"
|
||||
|
||||
#import <AdjustSdk/Adjust.h>
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger(LOG_IOS, "IOSAdjustHelper");
|
||||
|
||||
} // namespace
|
||||
|
||||
void IOSAdjustHelper::initialize() {
|
||||
|
||||
NSString *adjustToken = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"ADJUST_SDK_TOKEN"];
|
||||
|
||||
if(adjustToken.length) {
|
||||
NSString *environment = Constants::inProduction() ? ADJEnvironmentProduction : ADJEnvironmentSandbox;
|
||||
ADJConfig *adjustConfig = [ADJConfig configWithAppToken:adjustToken
|
||||
environment:environment];
|
||||
[adjustConfig setLogLevel:ADJLogLevelDebug];
|
||||
[Adjust appDidLaunch:adjustConfig];
|
||||
}
|
||||
}
|
||||
|
||||
void IOSAdjustHelper::trackEvent(const QString& eventToken) {
|
||||
NSString *adjustToken = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"ADJUST_SDK_TOKEN"];
|
||||
|
||||
if(adjustToken.length) {
|
||||
ADJEvent *event = [ADJEvent eventWithEventToken:eventToken.toNSString()];
|
||||
[Adjust trackEvent:event];
|
||||
}
|
||||
}
|
23
client/platforms/ios/iosauthenticationlistener.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IOSAUTHENTICATIONLISTENER_H
|
||||
#define IOSAUTHENTICATIONLISTENER_H
|
||||
|
||||
#include "authenticationlistener.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class IOSAuthenticationListener final : public AuthenticationListener {
|
||||
Q_DISABLE_COPY_MOVE(IOSAuthenticationListener)
|
||||
|
||||
public:
|
||||
IOSAuthenticationListener(QObject* parent);
|
||||
~IOSAuthenticationListener();
|
||||
|
||||
void start(const QString& codeChallenge, const QString& codeChallengeMethod,
|
||||
const QString& emailAddress) override;
|
||||
};
|
||||
|
||||
#endif // IOSAUTHENTICATIONLISTENER_H
|
139
client/platforms/ios/iosauthenticationlistener.mm
Normal file
|
@ -0,0 +1,139 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "iosauthenticationlistener.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "mozillavpn.h"
|
||||
#include "qmlengineholder.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QtGui/qpa/qplatformnativeinterface.h>
|
||||
#include <QtGui>
|
||||
#include <QWindow>
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AuthenticationServices/ASWebAuthenticationSession.h>
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger({LOG_IOS, LOG_MAIN}, "IOSAuthenticationListener");
|
||||
|
||||
ASWebAuthenticationSession* session = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
@interface ContextProvider : NSObject <ASWebAuthenticationPresentationContextProviding> {
|
||||
UIView* m_view;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation ContextProvider
|
||||
|
||||
- (id)initWithUIView:(UIView*)uiView {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
m_view = uiView;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
# pragma mark - ASWebAuthenticationPresentationContextProviding
|
||||
- (nonnull ASPresentationAnchor)presentationAnchorForWebAuthenticationSession:
|
||||
(nonnull ASWebAuthenticationSession*)session API_AVAILABLE(ios(13.0)) {
|
||||
return m_view.window;
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
IOSAuthenticationListener::IOSAuthenticationListener(QObject* parent)
|
||||
: AuthenticationListener(parent) {
|
||||
MVPN_COUNT_CTOR(IOSAuthenticationListener);
|
||||
}
|
||||
|
||||
IOSAuthenticationListener::~IOSAuthenticationListener() {
|
||||
MVPN_COUNT_DTOR(IOSAuthenticationListener);
|
||||
}
|
||||
|
||||
void IOSAuthenticationListener::start(const QString& codeChallenge,
|
||||
const QString& codeChallengeMethod,
|
||||
const QString& emailAddress) {
|
||||
logger.debug() << "IOSAuthenticationListener initialize";
|
||||
|
||||
QUrl url(createAuthenticationUrl(AmneziaVPN::AuthenticationInBrowser, codeChallenge,
|
||||
codeChallengeMethod, emailAddress));
|
||||
QUrlQuery query(url.query());
|
||||
query.addQueryItem("platform", "ios");
|
||||
url.setQuery(query);
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
logger.debug() << "Authentication URL:" << url.toString();
|
||||
#endif
|
||||
|
||||
if (session) {
|
||||
[session dealloc];
|
||||
session = nullptr;
|
||||
}
|
||||
|
||||
session = [[ASWebAuthenticationSession alloc]
|
||||
initWithURL:url.toNSURL()
|
||||
callbackURLScheme:@"mozilla-vpn"
|
||||
completionHandler:^(NSURL* _Nullable callbackURL, NSError* _Nullable error) {
|
||||
[session dealloc];
|
||||
session = nullptr;
|
||||
|
||||
if (error) {
|
||||
logger.error() << "Authentication failed:"
|
||||
<< QString::fromNSString([error localizedDescription]);
|
||||
logger.error() << "Code:" << [error code];
|
||||
logger.error() << "Suggestion:"
|
||||
<< QString::fromNSString([error localizedRecoverySuggestion]);
|
||||
logger.error() << "Reason:" << QString::fromNSString([error localizedFailureReason]);
|
||||
|
||||
if ([error code] == ASWebAuthenticationSessionErrorCodeCanceledLogin) {
|
||||
emit abortedByUser();
|
||||
} else {
|
||||
emit failed(ErrorHandler::RemoteServiceError);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl callbackUrl = QUrl::fromNSURL(callbackURL);
|
||||
logger.debug() << "Authentication completed";
|
||||
|
||||
Q_ASSERT(callbackUrl.hasQuery());
|
||||
|
||||
QUrlQuery callbackUrlQuery(callbackUrl.query());
|
||||
Q_ASSERT(callbackUrlQuery.hasQueryItem("code"));
|
||||
QString code = callbackUrlQuery.queryItemValue("code");
|
||||
emit completed(code);
|
||||
}];
|
||||
|
||||
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
|
||||
QObject* rootObject = QmlEngineHolder::instance()->engine()->rootObjects().first();
|
||||
QWindow* window = qobject_cast<QWindow*>(rootObject);
|
||||
Q_ASSERT(window);
|
||||
|
||||
UIView* view = static_cast<UIView*>(
|
||||
QGuiApplication::platformNativeInterface()->nativeResourceForWindow("uiview", window));
|
||||
|
||||
if (@available(iOS 13, *)) {
|
||||
session.presentationContextProvider = [[ContextProvider alloc] initWithUIView:view];
|
||||
}
|
||||
#endif
|
||||
|
||||
if (![session start]) {
|
||||
[session dealloc];
|
||||
session = nullptr;
|
||||
|
||||
logger.error() << "Authentication failed: session doesn't start.";
|
||||
emit failed(ErrorHandler::RemoteServiceError);
|
||||
}
|
||||
}
|
39
client/platforms/ios/ioscontroller.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IOSCONTROLLER_H
|
||||
#define IOSCONTROLLER_H
|
||||
|
||||
#include "controllerimpl.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class IOSController final : public ControllerImpl {
|
||||
Q_DISABLE_COPY_MOVE(IOSController)
|
||||
|
||||
public:
|
||||
IOSController();
|
||||
~IOSController();
|
||||
|
||||
void initialize(const Device* device, const Keys* keys) override;
|
||||
|
||||
void activate(const QList<Server>& serverList, const Device* device,
|
||||
const Keys* keys,
|
||||
const QList<IPAddressRange>& allowedIPAddressRanges,
|
||||
const QList<QString>& vpnDisabledApps,
|
||||
const QHostAddress& dnsServer, Reason reason) override;
|
||||
|
||||
void deactivate(Reason reason) override;
|
||||
|
||||
void checkStatus() override;
|
||||
|
||||
void getBackendLogs(std::function<void(const QString&)>&& callback) override;
|
||||
|
||||
void cleanupBackendLogs() override;
|
||||
|
||||
private:
|
||||
bool m_checkingStatus = false;
|
||||
};
|
||||
|
||||
#endif // IOSCONTROLLER_H
|
240
client/platforms/ios/ioscontroller.mm
Normal file
|
@ -0,0 +1,240 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ioscontroller.h"
|
||||
#include "Mozilla_VPN-Swift.h"
|
||||
#include "device.h"
|
||||
#include "ipaddressrange.h"
|
||||
#include "keys.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "mozillavpn.h"
|
||||
#include "server.h"
|
||||
#include "settingsholder.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFile>
|
||||
#include <QHostAddress>
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger({LOG_IOS, LOG_CONTROLLER}, "IOSController");
|
||||
|
||||
// Our Swift singleton.
|
||||
IOSControllerImpl* impl = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
IOSController::IOSController() {
|
||||
MVPN_COUNT_CTOR(IOSController);
|
||||
|
||||
logger.debug() << "created";
|
||||
|
||||
Q_ASSERT(!impl);
|
||||
}
|
||||
|
||||
IOSController::~IOSController() {
|
||||
MVPN_COUNT_DTOR(IOSController);
|
||||
|
||||
logger.debug() << "deallocated";
|
||||
|
||||
if (impl) {
|
||||
[impl dealloc];
|
||||
impl = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void IOSController::initialize(const Device* device, const Keys* keys) {
|
||||
Q_ASSERT(!impl);
|
||||
Q_UNUSED(device);
|
||||
|
||||
logger.debug() << "Initializing Swift Controller";
|
||||
|
||||
static bool creating = false;
|
||||
// No nested creation!
|
||||
Q_ASSERT(creating == false);
|
||||
creating = true;
|
||||
|
||||
QByteArray key = QByteArray::fromBase64(keys->privateKey().toLocal8Bit());
|
||||
|
||||
impl = [[IOSControllerImpl alloc] initWithBundleID:@VPN_NE_BUNDLEID
|
||||
privateKey:key.toNSData()
|
||||
deviceIpv4Address:device->ipv4Address().toNSString()
|
||||
deviceIpv6Address:device->ipv6Address().toNSString()
|
||||
closure:^(ConnectionState state, NSDate* date) {
|
||||
logger.debug() << "Creation completed with connection state:" << state;
|
||||
creating = false;
|
||||
|
||||
switch (state) {
|
||||
case ConnectionStateError: {
|
||||
[impl dealloc];
|
||||
impl = nullptr;
|
||||
emit initialized(false, false, QDateTime());
|
||||
return;
|
||||
}
|
||||
case ConnectionStateConnected: {
|
||||
Q_ASSERT(date);
|
||||
QDateTime qtDate(QDateTime::fromNSDate(date));
|
||||
emit initialized(true, true, qtDate);
|
||||
return;
|
||||
}
|
||||
case ConnectionStateDisconnected:
|
||||
// Just in case we are connecting, let's call disconnect.
|
||||
[impl disconnect];
|
||||
emit initialized(true, false, QDateTime());
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback:^(BOOL a_connected) {
|
||||
logger.debug() << "State changed: " << a_connected;
|
||||
if (a_connected) {
|
||||
emit connected();
|
||||
return;
|
||||
}
|
||||
|
||||
emit disconnected();
|
||||
}];
|
||||
}
|
||||
|
||||
void IOSController::activate(const QList<Server>& serverList, const Device* device,
|
||||
const Keys* keys, const QList<IPAddressRange>& allowedIPAddressRanges,
|
||||
const QList<QString>& vpnDisabledApps, const QHostAddress& dnsServer,
|
||||
Reason reason) {
|
||||
Q_UNUSED(device);
|
||||
Q_UNUSED(keys);
|
||||
|
||||
Q_ASSERT(serverList.length() == 1);
|
||||
const Server& server = serverList[0];
|
||||
|
||||
// This feature is not supported on macos/ios yet.
|
||||
Q_ASSERT(vpnDisabledApps.isEmpty());
|
||||
|
||||
logger.debug() << "IOSController activating" << server.hostname();
|
||||
|
||||
if (!impl) {
|
||||
logger.error() << "Controller not correctly initialized";
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray<VPNIPAddressRange*>* allowedIPAddressRangesNS =
|
||||
[NSMutableArray<VPNIPAddressRange*> arrayWithCapacity:allowedIPAddressRanges.length()];
|
||||
for (const IPAddressRange& i : allowedIPAddressRanges) {
|
||||
VPNIPAddressRange* range =
|
||||
[[VPNIPAddressRange alloc] initWithAddress:i.ipAddress().toNSString()
|
||||
networkPrefixLength:i.range()
|
||||
isIpv6:i.type() == IPAddressRange::IPv6];
|
||||
[allowedIPAddressRangesNS addObject:[range autorelease]];
|
||||
}
|
||||
|
||||
[impl connectWithDnsServer:dnsServer.toString().toNSString()
|
||||
serverIpv6Gateway:server.ipv6Gateway().toNSString()
|
||||
serverPublicKey:server.publicKey().toNSString()
|
||||
serverIpv4AddrIn:server.ipv4AddrIn().toNSString()
|
||||
serverPort:server.choosePort()
|
||||
allowedIPAddressRanges:allowedIPAddressRangesNS
|
||||
ipv6Enabled:SettingsHolder::instance()->ipv6Enabled()
|
||||
reason:reason
|
||||
failureCallback:^() {
|
||||
logger.error() << "IOSSWiftController - connection failed";
|
||||
emit disconnected();
|
||||
}];
|
||||
}
|
||||
|
||||
void IOSController::deactivate(Reason reason) {
|
||||
logger.debug() << "IOSController deactivated";
|
||||
|
||||
if (reason != ReasonNone) {
|
||||
logger.debug() << "We do not need to disable the VPN for switching or connection check.";
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!impl) {
|
||||
logger.error() << "Controller not correctly initialized";
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
[impl disconnect];
|
||||
}
|
||||
|
||||
void IOSController::checkStatus() {
|
||||
logger.debug() << "Checking status";
|
||||
|
||||
if (m_checkingStatus) {
|
||||
logger.warning() << "We are still waiting for the previous status.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!impl) {
|
||||
logger.error() << "Controller not correctly initialized";
|
||||
return;
|
||||
}
|
||||
|
||||
m_checkingStatus = true;
|
||||
|
||||
[impl checkStatusWithCallback:^(NSString* serverIpv4Gateway, NSString* deviceIpv4Address,
|
||||
NSString* configString) {
|
||||
QString config = QString::fromNSString(configString);
|
||||
|
||||
m_checkingStatus = false;
|
||||
|
||||
if (config.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t txBytes = 0;
|
||||
uint64_t rxBytes = 0;
|
||||
|
||||
QStringList lines = config.split("\n");
|
||||
for (const QString& line : lines) {
|
||||
if (line.startsWith("tx_bytes=")) {
|
||||
txBytes = line.split("=")[1].toULongLong();
|
||||
} else if (line.startsWith("rx_bytes=")) {
|
||||
rxBytes = line.split("=")[1].toULongLong();
|
||||
}
|
||||
|
||||
if (txBytes && rxBytes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway)
|
||||
<< "DeviceIpv4Address:" << QString::fromNSString(deviceIpv4Address)
|
||||
<< "RxBytes:" << rxBytes << "TxBytes:" << txBytes;
|
||||
emit statusUpdated(QString::fromNSString(serverIpv4Gateway),
|
||||
QString::fromNSString(deviceIpv4Address), txBytes, rxBytes);
|
||||
}];
|
||||
}
|
||||
|
||||
void IOSController::getBackendLogs(std::function<void(const QString&)>&& a_callback) {
|
||||
std::function<void(const QString&)> callback = std::move(a_callback);
|
||||
|
||||
QString groupId(GROUP_ID);
|
||||
NSURL* groupPath = [[NSFileManager defaultManager]
|
||||
containerURLForSecurityApplicationGroupIdentifier:groupId.toNSString()];
|
||||
|
||||
NSURL* path = [groupPath URLByAppendingPathComponent:@"networkextension.log"];
|
||||
|
||||
QFile file(QString::fromNSString([path path]));
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
callback("Network extension log file missing or unreadable.");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray content = file.readAll();
|
||||
callback(content);
|
||||
}
|
||||
|
||||
void IOSController::cleanupBackendLogs() {
|
||||
QString groupId(GROUP_ID);
|
||||
NSURL* groupPath = [[NSFileManager defaultManager]
|
||||
containerURLForSecurityApplicationGroupIdentifier:groupId.toNSString()];
|
||||
|
||||
NSURL* path = [groupPath URLByAppendingPathComponent:@"networkextension.log"];
|
||||
|
||||
QFile file(QString::fromNSString([path path]));
|
||||
file.remove();
|
||||
}
|
289
client/platforms/ios/ioscontroller.swift
Normal file
|
@ -0,0 +1,289 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
let vpnName = "Mozilla VPN"
|
||||
var vpnBundleID = "";
|
||||
|
||||
@objc class VPNIPAddressRange : NSObject {
|
||||
public var address: NSString = ""
|
||||
public var networkPrefixLength: UInt8 = 0
|
||||
public var isIpv6: Bool = false
|
||||
|
||||
@objc init(address: NSString, networkPrefixLength: UInt8, isIpv6: Bool) {
|
||||
super.init()
|
||||
|
||||
self.address = address
|
||||
self.networkPrefixLength = networkPrefixLength
|
||||
self.isIpv6 = isIpv6
|
||||
}
|
||||
}
|
||||
|
||||
public class IOSControllerImpl : NSObject {
|
||||
|
||||
private var tunnel: NETunnelProviderManager? = nil
|
||||
private var stateChangeCallback: ((Bool) -> Void?)? = nil
|
||||
private var privateKey : PrivateKey? = nil
|
||||
private var deviceIpv4Address: String? = nil
|
||||
private var deviceIpv6Address: String? = nil
|
||||
|
||||
@objc enum ConnectionState: Int { case Error, Connected, Disconnected }
|
||||
|
||||
@objc init(bundleID: String, privateKey: Data, deviceIpv4Address: String, deviceIpv6Address: String, closure: @escaping (ConnectionState, Date?) -> Void, callback: @escaping (Bool) -> Void) {
|
||||
super.init()
|
||||
|
||||
Logger.configureGlobal(tagged: "APP", withFilePath: "")
|
||||
|
||||
vpnBundleID = bundleID;
|
||||
precondition(!vpnBundleID.isEmpty)
|
||||
|
||||
stateChangeCallback = callback
|
||||
self.privateKey = PrivateKey(rawValue: privateKey)
|
||||
self.deviceIpv4Address = deviceIpv4Address
|
||||
self.deviceIpv6Address = deviceIpv6Address
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil)
|
||||
|
||||
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
|
||||
if let error = error {
|
||||
Logger.global?.log(message: "Loading from preference failed: \(error)")
|
||||
closure(ConnectionState.Error, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if self == nil {
|
||||
Logger.global?.log(message: "We are shutting down.")
|
||||
return
|
||||
}
|
||||
|
||||
let nsManagers = managers ?? []
|
||||
Logger.global?.log(message: "We have received \(nsManagers.count) managers.")
|
||||
|
||||
let tunnel = nsManagers.first(where: IOSControllerImpl.isOurManager(_:))
|
||||
if tunnel == nil {
|
||||
Logger.global?.log(message: "Creating the tunnel")
|
||||
self!.tunnel = NETunnelProviderManager()
|
||||
closure(ConnectionState.Disconnected, nil)
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Tunnel already exists")
|
||||
|
||||
self!.tunnel = tunnel
|
||||
if tunnel?.connection.status == .connected {
|
||||
closure(ConnectionState.Connected, tunnel?.connection.connectedDate)
|
||||
} else {
|
||||
closure(ConnectionState.Disconnected, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func vpnStatusDidChange(notification: Notification) {
|
||||
guard let session = (notification.object as? NETunnelProviderSession), tunnel?.connection == session else { return }
|
||||
|
||||
switch session.status {
|
||||
case .connected:
|
||||
Logger.global?.log(message: "STATE CHANGED: connected")
|
||||
case .connecting:
|
||||
Logger.global?.log(message: "STATE CHANGED: connecting")
|
||||
case .disconnected:
|
||||
Logger.global?.log(message: "STATE CHANGED: disconnected")
|
||||
case .disconnecting:
|
||||
Logger.global?.log(message: "STATE CHANGED: disconnecting")
|
||||
case .invalid:
|
||||
Logger.global?.log(message: "STATE CHANGED: invalid")
|
||||
case .reasserting:
|
||||
Logger.global?.log(message: "STATE CHANGED: reasserting")
|
||||
default:
|
||||
Logger.global?.log(message: "STATE CHANGED: unknown status")
|
||||
}
|
||||
|
||||
// We care about "unknown" state changes.
|
||||
if (session.status != .connected && session.status != .disconnected) {
|
||||
return
|
||||
}
|
||||
|
||||
stateChangeCallback?(session.status == .connected)
|
||||
}
|
||||
|
||||
private static func isOurManager(_ manager: NETunnelProviderManager) -> Bool {
|
||||
guard
|
||||
let proto = manager.protocolConfiguration,
|
||||
let tunnelProto = proto as? NETunnelProviderProtocol
|
||||
else {
|
||||
Logger.global?.log(message: "Ignoring manager because the proto is invalid.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (tunnelProto.providerBundleIdentifier == nil) {
|
||||
Logger.global?.log(message: "Ignoring manager because the bundle identifier is null.")
|
||||
return false
|
||||
}
|
||||
|
||||
if (tunnelProto.providerBundleIdentifier != vpnBundleID) {
|
||||
Logger.global?.log(message: "Ignoring manager because the bundle identifier doesn't match.")
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Found the manager with the correct bundle identifier: \(tunnelProto.providerBundleIdentifier!)")
|
||||
return true
|
||||
}
|
||||
|
||||
@objc func connect(dnsServer: String, serverIpv6Gateway: String, serverPublicKey: String, serverIpv4AddrIn: String, serverPort: Int, allowedIPAddressRanges: Array<VPNIPAddressRange>, ipv6Enabled: Bool, reason: Int, failureCallback: @escaping () -> Void) {
|
||||
Logger.global?.log(message: "Connecting")
|
||||
assert(tunnel != nil)
|
||||
|
||||
// Let's remove the previous config if it exists.
|
||||
(tunnel!.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
||||
|
||||
let keyData = PublicKey(base64Key: serverPublicKey)!
|
||||
let dnsServerIP = IPv4Address(dnsServer)
|
||||
let ipv6GatewayIP = IPv6Address(serverIpv6Gateway)
|
||||
|
||||
var peerConfiguration = PeerConfiguration(publicKey: keyData)
|
||||
peerConfiguration.endpoint = Endpoint(from: serverIpv4AddrIn + ":\(serverPort )")
|
||||
peerConfiguration.allowedIPs = []
|
||||
|
||||
allowedIPAddressRanges.forEach {
|
||||
if (!$0.isIpv6) {
|
||||
peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv4Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength))
|
||||
} else if (ipv6Enabled) {
|
||||
peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv6Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength))
|
||||
}
|
||||
}
|
||||
|
||||
var peerConfigurations: [PeerConfiguration] = []
|
||||
peerConfigurations.append(peerConfiguration)
|
||||
|
||||
var interface = InterfaceConfiguration(privateKey: privateKey!)
|
||||
|
||||
if let ipv4Address = IPAddressRange(from: deviceIpv4Address!),
|
||||
let ipv6Address = IPAddressRange(from: deviceIpv6Address!) {
|
||||
interface.addresses = [ipv4Address]
|
||||
if (ipv6Enabled) {
|
||||
interface.addresses.append(ipv6Address)
|
||||
}
|
||||
}
|
||||
interface.dns = [ DNSServer(address: dnsServerIP!)]
|
||||
|
||||
if (ipv6Enabled) {
|
||||
interface.dns.append(DNSServer(address: ipv6GatewayIP!))
|
||||
}
|
||||
|
||||
let config = TunnelConfiguration(name: vpnName, interface: interface, peers: peerConfigurations)
|
||||
|
||||
self.configureTunnel(config: config, reason: reason, failureCallback: failureCallback)
|
||||
}
|
||||
|
||||
func configureTunnel(config: TunnelConfiguration, reason: Int, failureCallback: @escaping () -> Void) {
|
||||
let proto = NETunnelProviderProtocol(tunnelConfiguration: config)
|
||||
proto!.providerBundleIdentifier = vpnBundleID
|
||||
|
||||
tunnel!.protocolConfiguration = proto
|
||||
tunnel!.localizedDescription = vpnName
|
||||
tunnel!.isEnabled = true
|
||||
|
||||
tunnel!.saveToPreferences { [unowned self] saveError in
|
||||
if let error = saveError {
|
||||
Logger.global?.log(message: "Connect Tunnel Save Error: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Saving the tunnel succeeded")
|
||||
|
||||
self.tunnel!.loadFromPreferences { error in
|
||||
if let error = error {
|
||||
Logger.global?.log(message: "Connect Tunnel Load Error: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global?.log(message: "Loading the tunnel succeeded")
|
||||
|
||||
do {
|
||||
if (reason == 1 /* ReasonSwitching */) {
|
||||
let settings = config.asWgQuickConfig()
|
||||
let settingsData = settings.data(using: .utf8)!
|
||||
try (self.tunnel!.connection as? NETunnelProviderSession)?
|
||||
.sendProviderMessage(settingsData) { data in
|
||||
guard let data = data,
|
||||
let configString = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
Logger.global?.log(message: "Failed to convert response to string")
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try (self.tunnel!.connection as? NETunnelProviderSession)?.startTunnel()
|
||||
}
|
||||
} catch let error {
|
||||
Logger.global?.log(message: "Something went wrong: \(error)")
|
||||
failureCallback()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func disconnect() {
|
||||
Logger.global?.log(message: "Disconnecting")
|
||||
assert(tunnel != nil)
|
||||
(tunnel!.connection as? NETunnelProviderSession)?.stopTunnel()
|
||||
}
|
||||
|
||||
@objc func checkStatus(callback: @escaping (String, String, String) -> Void) {
|
||||
Logger.global?.log(message: "Check status")
|
||||
assert(tunnel != nil)
|
||||
|
||||
let proto = tunnel!.protocolConfiguration as? NETunnelProviderProtocol
|
||||
if proto == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
let tunnelConfiguration = proto?.asTunnelConfiguration()
|
||||
if tunnelConfiguration == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
let serverIpv4Gateway = tunnelConfiguration?.interface.dns[0].address
|
||||
if serverIpv4Gateway == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
let deviceIpv4Address = tunnelConfiguration?.interface.addresses[0].address
|
||||
if deviceIpv4Address == nil {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
guard let session = tunnel?.connection as? NETunnelProviderSession
|
||||
else {
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in
|
||||
guard let data = data,
|
||||
let configString = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
Logger.global?.log(message: "Failed to convert data to string")
|
||||
callback("", "", "")
|
||||
return
|
||||
}
|
||||
|
||||
callback("\(serverIpv4Gateway!)", "\(deviceIpv4Address!)", configString)
|
||||
}
|
||||
} catch {
|
||||
Logger.global?.log(message: "Failed to retrieve data from session")
|
||||
callback("", "", "")
|
||||
}
|
||||
}
|
||||
}
|
15
client/platforms/ios/iosdatamigration.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
/* 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 IOSDATAMIGRATION_H
|
||||
#define IOSDATAMIGRATION_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class IOSDataMigration final {
|
||||
public:
|
||||
static void migrate();
|
||||
};
|
||||
|
||||
#endif // IOSDATAMIGRATION_H
|
172
client/platforms/ios/iosdatamigration.mm
Normal file
|
@ -0,0 +1,172 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "iosdatamigration.h"
|
||||
#include "device.h"
|
||||
#include "logger.h"
|
||||
#include "mozillavpn.h"
|
||||
#include "user.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_IOS, "IOSDataMigration");
|
||||
|
||||
void migrateUserDefaultData() {
|
||||
AmneziaVPN* vpn = AmneziaVPN::instance();
|
||||
Q_ASSERT(vpn);
|
||||
|
||||
NSUserDefaults* sud = [NSUserDefaults standardUserDefaults];
|
||||
if (!sud) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSData* userData = [sud dataForKey:@"user"];
|
||||
if (userData) {
|
||||
QByteArray json = QByteArray::fromNSData(userData);
|
||||
if (!json.isEmpty()) {
|
||||
logger.debug() << "User data to be migrated";
|
||||
vpn->accountChecked(json);
|
||||
}
|
||||
}
|
||||
|
||||
NSData* deviceData = [sud dataForKey:@"device"];
|
||||
if (deviceData) {
|
||||
QByteArray json = QByteArray::fromNSData(deviceData);
|
||||
logger.debug() << "Device data to be migrated";
|
||||
// Nothing has to be done here because the device data is part of the user data.
|
||||
}
|
||||
|
||||
NSData* serversData = [sud dataForKey:@"vpnServers"];
|
||||
if (serversData) {
|
||||
QByteArray json = QByteArray::fromNSData(serversData);
|
||||
if (!json.isEmpty()) {
|
||||
logger.debug() << "Server list data to be migrated";
|
||||
|
||||
// We need to wrap the server list in a object to make it similar to the REST API response.
|
||||
QJsonDocument serverList = QJsonDocument::fromJson(json);
|
||||
if (!serverList.isArray()) {
|
||||
logger.error() << "Server list should be an array!";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject countriesObj;
|
||||
countriesObj.insert("countries", QJsonValue(serverList.array()));
|
||||
|
||||
QJsonDocument doc;
|
||||
doc.setObject(countriesObj);
|
||||
if (!vpn->setServerList(doc.toJson())) {
|
||||
logger.error() << "Server list cannot be imported";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSData* selectedCityData = [sud dataForKey:@"selectedCity"];
|
||||
if (selectedCityData) {
|
||||
QByteArray json = QByteArray::fromNSData(selectedCityData);
|
||||
logger.debug() << "SelectedCity data to be migrated" << json;
|
||||
// Nothing has to be done here because the device data is part of the user data.
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(json);
|
||||
if (!doc.isObject()) {
|
||||
logger.error() << "SelectedCity should be an object";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject obj = doc.object();
|
||||
QJsonValue code = obj.value("flagCode");
|
||||
if (!code.isString()) {
|
||||
logger.error() << "SelectedCity code should be a string";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue name = obj.value("code");
|
||||
if (!name.isString()) {
|
||||
logger.error() << "SelectedCity name should be a string";
|
||||
return;
|
||||
}
|
||||
|
||||
ServerData serverData;
|
||||
if (vpn->serverCountryModel()->pickIfExists(code.toString(), name.toString(), serverData)) {
|
||||
logger.debug() << "ServerCity found";
|
||||
serverData.writeSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void migrateKeychainData() {
|
||||
NSData* service = [@"org.mozilla.guardian.credentials" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSMutableDictionary* query = [[NSMutableDictionary alloc] init];
|
||||
|
||||
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
|
||||
[query setObject:service forKey:(id)kSecAttrService];
|
||||
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
|
||||
[query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
|
||||
|
||||
NSData* dataNS = NULL;
|
||||
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)(void*)&dataNS);
|
||||
[query release];
|
||||
|
||||
if (status != noErr) {
|
||||
logger.error() << "No credentials found";
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data = QByteArray::fromNSData(dataNS);
|
||||
logger.debug() << "Credentials:" << logger.sensitive(data);
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(data);
|
||||
if (!json.isObject()) {
|
||||
logger.error() << "JSON object expected";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject obj = json.object();
|
||||
QJsonValue deviceKeyValue = obj.value("deviceKeys");
|
||||
if (!deviceKeyValue.isObject()) {
|
||||
logger.error() << "JSON object should have a deviceKeys object";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject deviceKeyObj = deviceKeyValue.toObject();
|
||||
QJsonValue publicKey = deviceKeyObj.value("publicKey");
|
||||
if (!publicKey.isString()) {
|
||||
logger.error() << "JSON deviceKey object should contain a publicKey value as string";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue privateKey = deviceKeyObj.value("privateKey");
|
||||
if (!privateKey.isString()) {
|
||||
logger.error() << "JSON deviceKey object should contain a privateKey value as string";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue token = obj.value("verificationToken");
|
||||
if (!token.isString()) {
|
||||
logger.error() << "JSON object should contain a verificationToken value s string";
|
||||
return;
|
||||
}
|
||||
|
||||
AmneziaVPN::instance()->deviceAdded(Device::currentDeviceName(), publicKey.toString(),
|
||||
privateKey.toString());
|
||||
|
||||
AmneziaVPN::instance()->setToken(token.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void IOSDataMigration::migrate() {
|
||||
logger.debug() << "IOS Data Migration";
|
||||
|
||||
migrateKeychainData();
|
||||
migrateUserDefaultData();
|
||||
}
|
249
client/platforms/ios/iosglue.mm
Normal file
|
@ -0,0 +1,249 @@
|
|||
/* 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
|
||||
// -----------------
|
||||
|
||||
#ifndef NETWORK_EXTENSION
|
||||
namespace {
|
||||
Logger logger(LOG_IOS, "IOSSGlue");
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
}
|
30
client/platforms/ios/iosiaphandler.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* 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 IOSIAPHANDLER_H
|
||||
#define IOSIAPHANDLER_H
|
||||
|
||||
#include "iaphandler.h"
|
||||
|
||||
class IOSIAPHandler final : public IAPHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(IOSIAPHandler)
|
||||
|
||||
public:
|
||||
explicit IOSIAPHandler(QObject* parent);
|
||||
~IOSIAPHandler();
|
||||
|
||||
public slots:
|
||||
void productRegistered(void* product);
|
||||
void processCompletedTransactions(const QStringList& ids);
|
||||
|
||||
protected:
|
||||
void nativeRegisterProducts() override;
|
||||
void nativeStartSubscription(Product* product) override;
|
||||
|
||||
private:
|
||||
void* m_delegate = nullptr;
|
||||
};
|
||||
|
||||
#endif // IOSIAPHANDLER_H
|
369
client/platforms/ios/iosiaphandler.mm
Normal file
|
@ -0,0 +1,369 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "platforms/ios/iosiaphandler.h"
|
||||
#include "constants.h"
|
||||
#include "iosutils.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "mozillavpn.h"
|
||||
#include "networkrequest.h"
|
||||
#include "settingsholder.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <StoreKit/StoreKit.h>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_IAP, "IOSIAPHandler");
|
||||
} // namespace
|
||||
|
||||
@interface IOSIAPHandlerDelegate
|
||||
: NSObject <SKRequestDelegate, SKProductsRequestDelegate, SKPaymentTransactionObserver> {
|
||||
IOSIAPHandler* m_handler;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation IOSIAPHandlerDelegate
|
||||
|
||||
- (id)initWithObject:(IOSIAPHandler*)handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
m_handler = handler;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)productsRequest:(nonnull SKProductsRequest*)request
|
||||
didReceiveResponse:(nonnull SKProductsResponse*)response {
|
||||
logger.debug() << "Registration completed";
|
||||
|
||||
if (response.invalidProductIdentifiers) {
|
||||
NSArray<NSString*>* products = response.invalidProductIdentifiers;
|
||||
logger.error() << "Registration failure" << [products count];
|
||||
|
||||
for (unsigned long i = 0, count = [products count]; i < count; ++i) {
|
||||
NSString* identifier = [products objectAtIndex:i];
|
||||
QMetaObject::invokeMethod(m_handler, "unknownProductRegistered", Qt::QueuedConnection,
|
||||
Q_ARG(QString, QString::fromNSString(identifier)));
|
||||
}
|
||||
}
|
||||
|
||||
NSArray<SKProduct*>* products = response.products;
|
||||
if (products) {
|
||||
logger.debug() << "Products registered" << [products count];
|
||||
|
||||
for (unsigned long i = 0, count = [products count]; i < count; ++i) {
|
||||
SKProduct* product = [[products objectAtIndex:i] retain];
|
||||
QMetaObject::invokeMethod(m_handler, "productRegistered", Qt::QueuedConnection,
|
||||
Q_ARG(void*, product));
|
||||
}
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(m_handler, "productsRegistrationCompleted", Qt::QueuedConnection);
|
||||
|
||||
[request release];
|
||||
}
|
||||
|
||||
- (void)paymentQueue:(nonnull SKPaymentQueue*)queue
|
||||
updatedTransactions:(nonnull NSArray<SKPaymentTransaction*>*)transactions {
|
||||
logger.debug() << "payment queue:" << [transactions count];
|
||||
|
||||
QStringList completedTransactionIds;
|
||||
bool failedTransactions = false;
|
||||
bool canceledTransactions = false;
|
||||
bool completedTransactions = false;
|
||||
|
||||
for (SKPaymentTransaction* transaction in transactions) {
|
||||
switch (transaction.transactionState) {
|
||||
case SKPaymentTransactionStateFailed:
|
||||
logger.error() << "transaction failed";
|
||||
|
||||
if (transaction.error.code == SKErrorPaymentCancelled) {
|
||||
canceledTransactions = true;
|
||||
} else {
|
||||
failedTransactions = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case SKPaymentTransactionStateRestored:
|
||||
[[fallthrough]];
|
||||
case SKPaymentTransactionStatePurchased: {
|
||||
QString identifier = QString::fromNSString(transaction.transactionIdentifier);
|
||||
QDateTime date = QDateTime::fromNSDate(transaction.transactionDate);
|
||||
logger.debug() << "transaction purchased - identifier: " << identifier
|
||||
<< "- date:" << date.toString();
|
||||
|
||||
if (transaction.transactionState == SKPaymentTransactionStateRestored) {
|
||||
SKPaymentTransaction* originalTransaction = transaction.originalTransaction;
|
||||
if (originalTransaction) {
|
||||
QString originalIdentifier =
|
||||
QString::fromNSString(originalTransaction.transactionIdentifier);
|
||||
QDateTime originalDate = QDateTime::fromNSDate(originalTransaction.transactionDate);
|
||||
logger.debug() << "original transaction identifier: " << originalIdentifier
|
||||
<< "- date:" << originalDate.toString();
|
||||
}
|
||||
}
|
||||
|
||||
completedTransactions = true;
|
||||
|
||||
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||
if (settingsHolder->hasSubscriptionTransaction(identifier)) {
|
||||
logger.warning() << "This transaction has already been processed. Let's ignore it.";
|
||||
} else {
|
||||
completedTransactionIds.append(identifier);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case SKPaymentTransactionStatePurchasing:
|
||||
logger.debug() << "transaction purchasing";
|
||||
break;
|
||||
case SKPaymentTransactionStateDeferred:
|
||||
logger.debug() << "transaction deferred";
|
||||
break;
|
||||
default:
|
||||
logger.warning() << "transaction unknwon state";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!completedTransactions && !canceledTransactions && !failedTransactions) {
|
||||
// Nothing completed, nothing restored, nothing failed. Just purchasing transactions.
|
||||
return;
|
||||
}
|
||||
|
||||
if (canceledTransactions) {
|
||||
logger.debug() << "Subscription canceled";
|
||||
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection);
|
||||
} else if (failedTransactions) {
|
||||
logger.error() << "Subscription failed";
|
||||
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection);
|
||||
} else if (completedTransactionIds.isEmpty()) {
|
||||
Q_ASSERT(completedTransactions);
|
||||
logger.debug() << "Subscription completed - but all the transactions are known";
|
||||
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection);
|
||||
} else if (AmneziaVPN::instance()->userAuthenticated()) {
|
||||
Q_ASSERT(completedTransactions);
|
||||
logger.debug() << "Subscription completed. Let's start the validation";
|
||||
QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection,
|
||||
Q_ARG(QStringList, completedTransactionIds));
|
||||
} else {
|
||||
Q_ASSERT(completedTransactions);
|
||||
logger.debug() << "Subscription completed - but the user is not authenticated yet";
|
||||
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
for (SKPaymentTransaction* transaction in transactions) {
|
||||
switch (transaction.transactionState) {
|
||||
case SKPaymentTransactionStateFailed:
|
||||
[[fallthrough]];
|
||||
case SKPaymentTransactionStateRestored:
|
||||
[[fallthrough]];
|
||||
case SKPaymentTransactionStatePurchased:
|
||||
[queue finishTransaction:transaction];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)requestDidFinish:(SKRequest*)request {
|
||||
logger.debug() << "Receipt refreshed correctly";
|
||||
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection,
|
||||
Q_ARG(QStringList, QStringList()));
|
||||
}
|
||||
|
||||
- (void)request:(SKRequest*)request didFailWithError:(NSError*)error {
|
||||
logger.error() << "Failed to refresh the receipt"
|
||||
<< QString::fromNSString(error.localizedDescription);
|
||||
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(m_handler, "subscriptionFailed", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
IOSIAPHandler::IOSIAPHandler(QObject* parent) : IAPHandler(parent) {
|
||||
MVPN_COUNT_CTOR(IOSIAPHandler);
|
||||
|
||||
m_delegate = [[IOSIAPHandlerDelegate alloc] initWithObject:this];
|
||||
[[SKPaymentQueue defaultQueue]
|
||||
addTransactionObserver:static_cast<IOSIAPHandlerDelegate*>(m_delegate)];
|
||||
}
|
||||
|
||||
IOSIAPHandler::~IOSIAPHandler() {
|
||||
MVPN_COUNT_DTOR(IOSIAPHandler);
|
||||
|
||||
IOSIAPHandlerDelegate* delegate = static_cast<IOSIAPHandlerDelegate*>(m_delegate);
|
||||
[[SKPaymentQueue defaultQueue] removeTransactionObserver:delegate];
|
||||
|
||||
[delegate dealloc];
|
||||
m_delegate = nullptr;
|
||||
}
|
||||
|
||||
void IOSIAPHandler::nativeRegisterProducts() {
|
||||
NSSet<NSString*>* productIdentifiers = [NSSet<NSString*> set];
|
||||
for (const Product& product : m_products) {
|
||||
productIdentifiers = [productIdentifiers setByAddingObject:product.m_name.toNSString()];
|
||||
}
|
||||
|
||||
logger.debug() << "We are about to register" << [productIdentifiers count] << "products";
|
||||
|
||||
SKProductsRequest* productsRequest =
|
||||
[[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
|
||||
|
||||
IOSIAPHandlerDelegate* delegate = static_cast<IOSIAPHandlerDelegate*>(m_delegate);
|
||||
productsRequest.delegate = delegate;
|
||||
[productsRequest start];
|
||||
}
|
||||
|
||||
void IOSIAPHandler::nativeStartSubscription(Product* product) {
|
||||
Q_ASSERT(product->m_extra);
|
||||
SKProduct* skProduct = static_cast<SKProduct*>(product->m_extra);
|
||||
SKPayment* payment = [SKPayment paymentWithProduct:skProduct];
|
||||
[[SKPaymentQueue defaultQueue] addPayment:payment];
|
||||
}
|
||||
|
||||
void IOSIAPHandler::productRegistered(void* a_product) {
|
||||
SKProduct* product = static_cast<SKProduct*>(a_product);
|
||||
|
||||
Q_ASSERT(m_productsRegistrationState == eRegistering);
|
||||
|
||||
logger.debug() << "Product registered";
|
||||
|
||||
NSString* nsProductIdentifier = [product productIdentifier];
|
||||
QString productIdentifier = QString::fromNSString(nsProductIdentifier);
|
||||
|
||||
Product* productData = findProduct(productIdentifier);
|
||||
Q_ASSERT(productData);
|
||||
|
||||
logger.debug() << "Id:" << productIdentifier;
|
||||
logger.debug() << "Title:" << QString::fromNSString([product localizedTitle]);
|
||||
logger.debug() << "Description:" << QString::fromNSString([product localizedDescription]);
|
||||
|
||||
QString priceValue;
|
||||
{
|
||||
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
|
||||
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
|
||||
[numberFormatter setNumberStyle:(NSNumberFormatterStyle)NSNumberFormatterCurrencyStyle];
|
||||
[numberFormatter setLocale:product.priceLocale];
|
||||
|
||||
NSString* price = [numberFormatter stringFromNumber:product.price];
|
||||
priceValue = QString::fromNSString(price);
|
||||
[numberFormatter release];
|
||||
}
|
||||
|
||||
logger.debug() << "Price:" << priceValue;
|
||||
|
||||
QString monthlyPriceValue;
|
||||
NSDecimalNumber* monthlyPriceNS = nullptr;
|
||||
{
|
||||
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
|
||||
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
|
||||
[numberFormatter setNumberStyle:(NSNumberFormatterStyle)NSNumberFormatterCurrencyStyle];
|
||||
[numberFormatter setLocale:product.priceLocale];
|
||||
|
||||
int32_t mounthCount = productTypeToMonthCount(productData->m_type);
|
||||
Q_ASSERT(mounthCount >= 1);
|
||||
|
||||
if (mounthCount == 1) {
|
||||
monthlyPriceNS = product.price;
|
||||
} else {
|
||||
NSDecimalNumber* divider = [[NSDecimalNumber alloc] initWithDouble:(double)mounthCount];
|
||||
monthlyPriceNS = [product.price decimalNumberByDividingBy:divider];
|
||||
[divider release];
|
||||
}
|
||||
|
||||
NSString* price = [numberFormatter stringFromNumber:monthlyPriceNS];
|
||||
monthlyPriceValue = QString::fromNSString(price);
|
||||
|
||||
[numberFormatter release];
|
||||
}
|
||||
|
||||
logger.debug() << "Monthly Price:" << monthlyPriceValue;
|
||||
|
||||
productData->m_price = priceValue;
|
||||
productData->m_monthlyPrice = monthlyPriceValue;
|
||||
productData->m_nonLocalizedMonthlyPrice = [monthlyPriceNS doubleValue];
|
||||
productData->m_extra = product;
|
||||
}
|
||||
|
||||
void IOSIAPHandler::processCompletedTransactions(const QStringList& ids) {
|
||||
logger.debug() << "process completed transactions";
|
||||
|
||||
if (m_subscriptionState != eActive) {
|
||||
logger.warning() << "Random transaction to be completed. Let's ignore it";
|
||||
return;
|
||||
}
|
||||
|
||||
QString receipt = IOSUtils::IAPReceipt();
|
||||
if (receipt.isEmpty()) {
|
||||
logger.warning() << "Empty receipt found";
|
||||
emit subscriptionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkRequest* request = NetworkRequest::createForIOSPurchase(this, receipt);
|
||||
|
||||
connect(request, &NetworkRequest::requestFailed,
|
||||
[this](QNetworkReply::NetworkError error, const QByteArray& data) {
|
||||
logger.error() << "Purchase request failed" << error;
|
||||
|
||||
if (m_subscriptionState != eActive) {
|
||||
logger.warning() << "We have been canceled in the meantime";
|
||||
return;
|
||||
}
|
||||
|
||||
stopSubscription();
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(data);
|
||||
if (!json.isObject()) {
|
||||
AmneziaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error));
|
||||
emit subscriptionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject obj = json.object();
|
||||
QJsonValue errorValue = obj.value("errno");
|
||||
if (!errorValue.isDouble()) {
|
||||
AmneziaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error));
|
||||
emit subscriptionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
int errorNumber = errorValue.toInt();
|
||||
if (errorNumber != 145) {
|
||||
AmneziaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error));
|
||||
emit subscriptionFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
emit alreadySubscribed();
|
||||
});
|
||||
|
||||
connect(request, &NetworkRequest::requestCompleted, [this, ids](const QByteArray&) {
|
||||
logger.debug() << "Purchase request completed";
|
||||
SettingsHolder::instance()->addSubscriptionTransactions(ids);
|
||||
|
||||
if (m_subscriptionState != eActive) {
|
||||
logger.warning() << "We have been canceled in the meantime";
|
||||
return;
|
||||
}
|
||||
|
||||
stopSubscription();
|
||||
emit subscriptionCompleted();
|
||||
});
|
||||
}
|
53
client/platforms/ios/ioslogger.swift
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
public class Logger {
|
||||
static var global: Logger?
|
||||
|
||||
var tag: String
|
||||
|
||||
init(tagged tag: String) {
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
deinit {}
|
||||
|
||||
func log(message: String) {
|
||||
write_msg_to_log(tag, message.trimmingCharacters(in: .newlines))
|
||||
}
|
||||
|
||||
func writeLog(to targetFile: String) -> Bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
static func configureGlobal(tagged tag: String, withFilePath filePath: String?) {
|
||||
if Logger.global != nil {
|
||||
return
|
||||
}
|
||||
|
||||
Logger.global = Logger(tagged: tag)
|
||||
|
||||
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
|
||||
|
||||
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
|
||||
appVersion += " (\(appBuild))"
|
||||
}
|
||||
|
||||
let goBackendVersion = WIREGUARD_GO_VERSION
|
||||
Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)")
|
||||
}
|
||||
}
|
||||
|
||||
func wg_log(_ type: OSLogType, staticMessage msg: StaticString) {
|
||||
os_log(msg, log: OSLog.default, type: type)
|
||||
Logger.global?.log(message: "\(msg)")
|
||||
}
|
||||
|
||||
func wg_log(_ type: OSLogType, message msg: String) {
|
||||
os_log("%{public}s", log: OSLog.default, type: type, msg)
|
||||
Logger.global?.log(message: msg)
|
||||
}
|
27
client/platforms/ios/iosnotificationhandler.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IOSNOTIFICATIONHANDLER_H
|
||||
#define IOSNOTIFICATIONHANDLER_H
|
||||
|
||||
#include "notificationhandler.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class IOSNotificationHandler final : public NotificationHandler {
|
||||
Q_DISABLE_COPY_MOVE(IOSNotificationHandler)
|
||||
|
||||
public:
|
||||
IOSNotificationHandler(QObject* parent);
|
||||
~IOSNotificationHandler();
|
||||
|
||||
protected:
|
||||
void notify(const QString& title, const QString& message,
|
||||
int timerSec) override;
|
||||
|
||||
private:
|
||||
void* m_delegate = nullptr;
|
||||
};
|
||||
|
||||
#endif // IOSNOTIFICATIONHANDLER_H
|
87
client/platforms/ios/iosnotificationhandler.mm
Normal file
|
@ -0,0 +1,87 @@
|
|||
/* 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"
|
||||
#include "leakdetector.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) {
|
||||
MVPN_COUNT_CTOR(IOSNotificationHandler);
|
||||
|
||||
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() { MVPN_COUNT_DTOR(IOSNotificationHandler); }
|
||||
|
||||
void IOSNotificationHandler::notify(const QString& title, const QString& message, int timerSec) {
|
||||
if (!m_delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = title.toNSString();
|
||||
content.body = message.toNSString();
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
|
||||
UNTimeIntervalNotificationTrigger* trigger =
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
|
||||
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"mozillavpn"
|
||||
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");
|
||||
}
|
||||
}];
|
||||
}
|
147
client/platforms/ios/iostunnel.swift
Normal file
|
@ -0,0 +1,147 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
import os
|
||||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
|
||||
private lazy var adapter: WireGuardAdapter = {
|
||||
return WireGuardAdapter(with: self) { logLevel, message in
|
||||
wg_log(logLevel.osLogLevel, message: message)
|
||||
}
|
||||
}()
|
||||
|
||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||
let activationAttemptId = options?["activationAttemptId"] as? String
|
||||
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
||||
|
||||
Logger.configureGlobal(tagged: "NET", withFilePath: FileManager.logFileURL?.path)
|
||||
|
||||
wg_log(.info, message: "Starting tunnel from the " + (activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||
|
||||
guard let tunnelProviderProtocol = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let tunnelConfiguration = tunnelProviderProtocol.asTunnelConfiguration() else {
|
||||
errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
||||
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
||||
return
|
||||
}
|
||||
|
||||
// Start the tunnel
|
||||
adapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
guard let adapterError = adapterError else {
|
||||
let interfaceName = self.adapter.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:
|
||||
// Must never happen
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||
|
||||
adapter.stop { error in
|
||||
ErrorNotifier.removeLastErrorFile()
|
||||
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||
}
|
||||
completionHandler()
|
||||
|
||||
#if os(macOS)
|
||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||
// sufficient quantities of users.
|
||||
exit(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
|
||||
if messageData.count == 1 && messageData[0] == 0 {
|
||||
adapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings = 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)
|
||||
adapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||
if let error = error {
|
||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
self.adapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings = settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
completionHandler(nil)
|
||||
}
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WireGuardLogLevel {
|
||||
var osLogLevel: OSLogType {
|
||||
switch self {
|
||||
case .verbose:
|
||||
return .debug
|
||||
case .error:
|
||||
return .error
|
||||
}
|
||||
}
|
||||
}
|
17
client/platforms/ios/iosutils.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IOSUTILS_H
|
||||
#define IOSUTILS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class IOSUtils final {
|
||||
public:
|
||||
static QString computerName();
|
||||
|
||||
static QString IAPReceipt();
|
||||
};
|
||||
|
||||
#endif // IOSUTILS_H
|
63
client/platforms/ios/iosutils.mm
Normal file
|
@ -0,0 +1,63 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "iosutils.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_IOS, "IOSUtils");
|
||||
}
|
||||
|
||||
// static
|
||||
QString IOSUtils::computerName() {
|
||||
NSString* name = [[UIDevice currentDevice] name];
|
||||
return QString::fromNSString(name);
|
||||
}
|
||||
|
||||
// static
|
||||
QString IOSUtils::IAPReceipt() {
|
||||
logger.debug() << "Retrieving IAP receipt";
|
||||
|
||||
NSURL* receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
|
||||
NSData* receipt = [NSData dataWithContentsOfURL:receiptURL];
|
||||
|
||||
// All the following is for debug only.
|
||||
NSString* path = [receiptURL path];
|
||||
Q_ASSERT(path);
|
||||
|
||||
logger.debug() << "Receipt URL:" << QString::fromNSString(path);
|
||||
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
Q_ASSERT(fileManager);
|
||||
|
||||
NSDictionary* fileAttributes = [fileManager attributesOfItemAtPath:path error:NULL];
|
||||
if (fileAttributes) {
|
||||
NSNumber* fileSize = [fileAttributes objectForKey:NSFileSize];
|
||||
if (fileSize) {
|
||||
logger.debug() << "File size:" << [fileSize unsignedLongLongValue];
|
||||
}
|
||||
|
||||
NSString* fileOwner = [fileAttributes objectForKey:NSFileOwnerAccountName];
|
||||
if (fileOwner) {
|
||||
logger.debug() << "Owner:" << QString::fromNSString(fileOwner);
|
||||
}
|
||||
|
||||
NSDate* fileModDate = [fileAttributes objectForKey:NSFileModificationDate];
|
||||
if (fileModDate) {
|
||||
logger.debug() << "Modification date:" << QDateTime::fromNSDate(fileModDate).toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!receipt) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
NSString* encodedReceipt = [receipt base64EncodedStringWithOptions:0];
|
||||
return QString::fromNSString(encodedReceipt);
|
||||
}
|
222
client/platforms/macos/daemon/dnsutilsmacos.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
/* 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 "dnsutilsmacos.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <systemconfiguration/scpreferences.h>
|
||||
#include <systemconfiguration/scdynamicstore.h>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "DnsUtilsMacos");
|
||||
}
|
||||
|
||||
DnsUtilsMacos::DnsUtilsMacos(QObject* parent) : DnsUtils(parent) {
|
||||
MVPN_COUNT_CTOR(DnsUtilsMacos);
|
||||
|
||||
m_scStore = SCDynamicStoreCreate(kCFAllocatorSystemDefault,
|
||||
CFSTR("mozillavpn"), nullptr, nullptr);
|
||||
if (m_scStore == nullptr) {
|
||||
logger.error() << "Failed to create system configuration store ref";
|
||||
}
|
||||
|
||||
logger.debug() << "DnsUtilsMacos created.";
|
||||
}
|
||||
|
||||
DnsUtilsMacos::~DnsUtilsMacos() {
|
||||
MVPN_COUNT_DTOR(DnsUtilsMacos);
|
||||
restoreResolvers();
|
||||
logger.debug() << "DnsUtilsMacos destroyed.";
|
||||
}
|
||||
|
||||
static QString cfParseString(CFTypeRef ref) {
|
||||
if (CFGetTypeID(ref) != CFStringGetTypeID()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
CFStringRef stringref = (CFStringRef)ref;
|
||||
CFRange range;
|
||||
range.location = 0;
|
||||
range.length = CFStringGetLength(stringref);
|
||||
if (range.length <= 0) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
UniChar* buf = (UniChar*)malloc(range.length * sizeof(UniChar));
|
||||
if (!buf) {
|
||||
return QString();
|
||||
}
|
||||
auto guard = qScopeGuard([&] { free(buf); });
|
||||
|
||||
CFStringGetCharacters(stringref, range, buf);
|
||||
return QString::fromUtf16(buf, range.length);
|
||||
}
|
||||
|
||||
static QStringList cfParseStringList(CFTypeRef ref) {
|
||||
if (CFGetTypeID(ref) != CFArrayGetTypeID()) {
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
CFArrayRef array = (CFArrayRef)ref;
|
||||
QStringList result;
|
||||
for (CFIndex i = 0; i < CFArrayGetCount(array); i++) {
|
||||
CFTypeRef value = CFArrayGetValueAtIndex(array, i);
|
||||
result.append(cfParseString(value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void cfDictSetString(CFMutableDictionaryRef dict, CFStringRef name,
|
||||
const QString& value) {
|
||||
if (value.isNull()) {
|
||||
return;
|
||||
}
|
||||
CFStringRef cfValue = CFStringCreateWithCString(
|
||||
kCFAllocatorSystemDefault, qUtf8Printable(value), kCFStringEncodingUTF8);
|
||||
CFDictionarySetValue(dict, name, cfValue);
|
||||
CFRelease(cfValue);
|
||||
}
|
||||
|
||||
static void cfDictSetStringList(CFMutableDictionaryRef dict, CFStringRef name,
|
||||
const QStringList& valueList) {
|
||||
if (valueList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CFMutableArrayRef array;
|
||||
array = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0,
|
||||
&kCFTypeArrayCallBacks);
|
||||
if (array == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QString& rstring : valueList) {
|
||||
CFStringRef cfAddr = CFStringCreateWithCString(kCFAllocatorSystemDefault,
|
||||
qUtf8Printable(rstring),
|
||||
kCFStringEncodingUTF8);
|
||||
CFArrayAppendValue(array, cfAddr);
|
||||
CFRelease(cfAddr);
|
||||
}
|
||||
CFDictionarySetValue(dict, name, array);
|
||||
CFRelease(array);
|
||||
}
|
||||
|
||||
bool DnsUtilsMacos::updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
Q_UNUSED(ifname);
|
||||
|
||||
// Get the list of current network services.
|
||||
CFArrayRef netServices = SCDynamicStoreCopyKeyList(
|
||||
m_scStore, CFSTR("Setup:/Network/Service/[0-9A-F-]+"));
|
||||
if (netServices == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto serviceGuard = qScopeGuard([&] { CFRelease(netServices); });
|
||||
|
||||
// Prepare the DNS configuration.
|
||||
CFMutableDictionaryRef dnsConfig = CFDictionaryCreateMutable(
|
||||
kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
auto configGuard = qScopeGuard([&] { CFRelease(dnsConfig); });
|
||||
QStringList list;
|
||||
for (const QHostAddress& addr : resolvers) {
|
||||
list.append(addr.toString());
|
||||
}
|
||||
cfDictSetStringList(dnsConfig, kSCPropNetDNSServerAddresses, list);
|
||||
cfDictSetString(dnsConfig, kSCPropNetDNSDomainName, "lan");
|
||||
|
||||
// Backup each network service's DNS config, and replace it with ours.
|
||||
for (CFIndex i = 0; i < CFArrayGetCount(netServices); i++) {
|
||||
QString service = cfParseString(CFArrayGetValueAtIndex(netServices, i));
|
||||
QString uuid = service.section('/', 3, 3);
|
||||
if (uuid.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
backupService(uuid);
|
||||
|
||||
logger.debug() << "Setting DNS config for" << uuid;
|
||||
CFStringRef dnsPath = CFStringCreateWithFormat(
|
||||
kCFAllocatorSystemDefault, nullptr,
|
||||
CFSTR("Setup:/Network/Service/%s/DNS"), qPrintable(uuid));
|
||||
if (!dnsPath) {
|
||||
continue;
|
||||
}
|
||||
SCDynamicStoreSetValue(m_scStore, dnsPath, dnsConfig);
|
||||
CFRelease(dnsPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DnsUtilsMacos::restoreResolvers() {
|
||||
for (const QString& uuid : m_prevServices.keys()) {
|
||||
CFStringRef path = CFStringCreateWithFormat(
|
||||
kCFAllocatorSystemDefault, nullptr,
|
||||
CFSTR("Setup:/Network/Service/%s/DNS"), qPrintable(uuid));
|
||||
|
||||
logger.debug() << "Restoring DNS config for" << uuid;
|
||||
const DnsBackup& backup = m_prevServices[uuid];
|
||||
if (backup.isValid()) {
|
||||
CFMutableDictionaryRef config;
|
||||
config = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0,
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
cfDictSetString(config, kSCPropNetDNSDomainName, backup.m_domain);
|
||||
cfDictSetStringList(config, kSCPropNetDNSSearchDomains, backup.m_search);
|
||||
cfDictSetStringList(config, kSCPropNetDNSServerAddresses,
|
||||
backup.m_servers);
|
||||
cfDictSetStringList(config, kSCPropNetDNSSortList, backup.m_sortlist);
|
||||
SCDynamicStoreSetValue(m_scStore, path, config);
|
||||
CFRelease(config);
|
||||
} else {
|
||||
SCDynamicStoreRemoveValue(m_scStore, path);
|
||||
}
|
||||
CFRelease(path);
|
||||
}
|
||||
|
||||
m_prevServices.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DnsUtilsMacos::backupService(const QString& uuid) {
|
||||
DnsBackup backup;
|
||||
CFStringRef path = CFStringCreateWithFormat(
|
||||
kCFAllocatorSystemDefault, nullptr,
|
||||
CFSTR("Setup:/Network/Service/%s/DNS"), qPrintable(uuid));
|
||||
CFDictionaryRef config =
|
||||
(CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore, path);
|
||||
auto serviceGuard = qScopeGuard([&] {
|
||||
if (config) {
|
||||
CFRelease(config);
|
||||
}
|
||||
CFRelease(path);
|
||||
});
|
||||
|
||||
// Parse the DNS protocol entry and save it for later.
|
||||
if (config) {
|
||||
CFTypeRef value;
|
||||
value = CFDictionaryGetValue(config, kSCPropNetDNSDomainName);
|
||||
if (value) {
|
||||
backup.m_domain = cfParseString(value);
|
||||
}
|
||||
value = CFDictionaryGetValue(config, kSCPropNetDNSServerAddresses);
|
||||
if (value) {
|
||||
backup.m_servers = cfParseStringList(value);
|
||||
}
|
||||
value = CFDictionaryGetValue(config, kSCPropNetDNSSearchDomains);
|
||||
if (value) {
|
||||
backup.m_search = cfParseStringList(value);
|
||||
}
|
||||
value = CFDictionaryGetValue(config, kSCPropNetDNSSortList);
|
||||
if (value) {
|
||||
backup.m_sortlist = cfParseStringList(value);
|
||||
}
|
||||
}
|
||||
|
||||
m_prevServices[uuid] = backup;
|
||||
}
|
51
client/platforms/macos/daemon/dnsutilsmacos.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DNSUTILSMACOS_H
|
||||
#define DNSUTILSMACOS_H
|
||||
|
||||
#include "dnsutils.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include <systemconfiguration/scdynamicstore.h>
|
||||
#include <systemconfiguration/systemconfiguration.h>
|
||||
|
||||
class DnsUtilsMacos final : public DnsUtils {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DnsUtilsMacos)
|
||||
|
||||
public:
|
||||
explicit DnsUtilsMacos(QObject* parent);
|
||||
virtual ~DnsUtilsMacos();
|
||||
bool updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) override;
|
||||
bool restoreResolvers() override;
|
||||
|
||||
private:
|
||||
void backupResolvers();
|
||||
void backupService(const QString& uuid);
|
||||
|
||||
private:
|
||||
class DnsBackup {
|
||||
public:
|
||||
DnsBackup() {}
|
||||
bool isValid() const {
|
||||
return !m_domain.isEmpty() || !m_search.isEmpty() ||
|
||||
!m_servers.isEmpty() || !m_sortlist.isEmpty();
|
||||
}
|
||||
|
||||
QString m_domain;
|
||||
QStringList m_search;
|
||||
QStringList m_servers;
|
||||
QStringList m_sortlist;
|
||||
};
|
||||
|
||||
SCDynamicStoreRef m_scStore = nullptr;
|
||||
QMap<QString, DnsBackup> m_prevServices;
|
||||
};
|
||||
|
||||
#endif // DNSUTILSMACOS_H
|
180
client/platforms/macos/daemon/iputilsmacos.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
/* 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 "iputilsmacos.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "macosdaemon.h"
|
||||
#include "daemon/wireguardutils.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_var.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/in_var.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
constexpr uint32_t ETH_MTU = 1500;
|
||||
constexpr uint32_t WG_MTU_OVERHEAD = 80;
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "IPUtilsMacos");
|
||||
}
|
||||
|
||||
IPUtilsMacos::IPUtilsMacos(QObject* parent) : IPUtils(parent) {
|
||||
MVPN_COUNT_CTOR(IPUtilsMacos);
|
||||
logger.debug() << "IPUtilsMacos created.";
|
||||
}
|
||||
|
||||
IPUtilsMacos::~IPUtilsMacos() {
|
||||
MVPN_COUNT_DTOR(IPUtilsMacos);
|
||||
logger.debug() << "IPUtilsMacos destroyed.";
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addInterfaceIPs(const InterfaceConfig& config) {
|
||||
if (!addIP4AddressToDevice(config)) {
|
||||
return false;
|
||||
}
|
||||
if (config.m_ipv6Enabled) {
|
||||
if (!addIP6AddressToDevice(config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
struct ifreq ifr;
|
||||
|
||||
// Create socket file descriptor to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// MTU
|
||||
strncpy(ifr.ifr_name, qPrintable(ifname), IFNAMSIZ);
|
||||
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
|
||||
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set MTU:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the interface flags
|
||||
strncpy(ifr.ifr_name, qPrintable(ifname), IFNAMSIZ);
|
||||
ret = ioctl(sockfd, SIOCGIFFLAGS, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to get interface flags:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Up
|
||||
ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
|
||||
ret = ioctl(sockfd, SIOCSIFFLAGS, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set device up:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
struct ifaliasreq ifr;
|
||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
||||
struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask;
|
||||
struct sockaddr_in* ifrBcast = (struct sockaddr_in*)&ifr.ifra_broadaddr;
|
||||
|
||||
// Name the interface and set family
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifra_name, qPrintable(ifname), IFNAMSIZ);
|
||||
|
||||
// Get the device address to add to interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv4Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
ifrAddr->sin_family = AF_INET;
|
||||
ifrAddr->sin_len = sizeof(struct sockaddr_in);
|
||||
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
|
||||
|
||||
// Set the netmask to /32
|
||||
ifrMask->sin_family = AF_INET;
|
||||
ifrMask->sin_len = sizeof(struct sockaddr_in);
|
||||
memset(&ifrMask->sin_addr, 0xff, sizeof(ifrMask->sin_addr));
|
||||
|
||||
// Set the broadcast address.
|
||||
ifrBcast->sin_family = AF_INET;
|
||||
ifrBcast->sin_len = sizeof(struct sockaddr_in);
|
||||
ifrBcast->sin_addr.s_addr =
|
||||
(ifrAddr->sin_addr.s_addr | ~ifrMask->sin_addr.s_addr);
|
||||
|
||||
// Create an IPv4 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCAIFADDR, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv4: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
struct in6_aliasreq ifr6;
|
||||
|
||||
// Name the interface and set family
|
||||
memset(&ifr6, 0, sizeof(ifr6));
|
||||
strncpy(ifr6.ifra_name, qPrintable(ifname), IFNAMSIZ);
|
||||
ifr6.ifra_addr.sin6_family = AF_INET6;
|
||||
ifr6.ifra_addr.sin6_len = sizeof(ifr6.ifra_addr);
|
||||
ifr6.ifra_lifetime.ia6t_vltime = ifr6.ifra_lifetime.ia6t_pltime = 0xffffffff;
|
||||
ifr6.ifra_prefixmask.sin6_family = AF_INET6;
|
||||
ifr6.ifra_prefixmask.sin6_len = sizeof(ifr6.ifra_prefixmask);
|
||||
memset(&ifr6.ifra_prefixmask.sin6_addr, 0xff, sizeof(struct in6_addr));
|
||||
|
||||
// Get the device address to add to interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv6Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
inet_pton(AF_INET6, deviceAddr, &ifr6.ifra_addr.sin6_addr);
|
||||
|
||||
// Create IPv4 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv6: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
28
client/platforms/macos/daemon/iputilsmacos.h
Normal 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 IPUTILSMACOS_H
|
||||
#define IPUTILSMACOS_H
|
||||
|
||||
#include "daemon/iputils.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
class IPUtilsMacos final : public IPUtils {
|
||||
public:
|
||||
IPUtilsMacos(QObject* parent);
|
||||
~IPUtilsMacos();
|
||||
bool addInterfaceIPs(const InterfaceConfig& config) override;
|
||||
bool setMTUAndUp(const InterfaceConfig& config) override;
|
||||
void setIfname(const QString& ifname) { m_ifname = ifname; }
|
||||
|
||||
private:
|
||||
bool addIP4AddressToDevice(const InterfaceConfig& config);
|
||||
bool addIP6AddressToDevice(const InterfaceConfig& config);
|
||||
|
||||
private:
|
||||
QString m_ifname;
|
||||
};
|
||||
|
||||
#endif // IPUTILSMACOS_H
|
74
client/platforms/macos/daemon/macosdaemon.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
/* 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 "macosdaemon.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "wgquickprocess.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QLocalSocket>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QTextStream>
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacOSDaemon");
|
||||
MacOSDaemon* s_daemon = nullptr;
|
||||
} // namespace
|
||||
|
||||
MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
|
||||
MVPN_COUNT_CTOR(MacOSDaemon);
|
||||
|
||||
logger.debug() << "Daemon created";
|
||||
|
||||
m_wgutils = new WireguardUtilsMacos(this);
|
||||
m_dnsutils = new DnsUtilsMacos(this);
|
||||
m_iputils = new IPUtilsMacos(this);
|
||||
|
||||
Q_ASSERT(s_daemon == nullptr);
|
||||
s_daemon = this;
|
||||
}
|
||||
|
||||
MacOSDaemon::~MacOSDaemon() {
|
||||
MVPN_COUNT_DTOR(MacOSDaemon);
|
||||
|
||||
logger.debug() << "Daemon released";
|
||||
|
||||
Q_ASSERT(s_daemon == this);
|
||||
s_daemon = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
MacOSDaemon* MacOSDaemon::instance() {
|
||||
Q_ASSERT(s_daemon);
|
||||
return s_daemon;
|
||||
}
|
||||
|
||||
QByteArray MacOSDaemon::getStatus() {
|
||||
logger.debug() << "Status request";
|
||||
|
||||
bool connected = m_connections.contains(0);
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "status");
|
||||
obj.insert("connected", connected);
|
||||
|
||||
if (connected) {
|
||||
const ConnectionState& state = m_connections.value(0).m_config;
|
||||
WireguardUtils::peerStatus status =
|
||||
m_wgutils->getPeerStatus(state.m_config.m_serverPublicKey);
|
||||
obj.insert("serverIpv4Gateway", state.m_config.m_serverIpv4Gateway);
|
||||
obj.insert("deviceIpv4Address", state.m_config.m_deviceIpv4Address);
|
||||
obj.insert("date", state.m_date.toString());
|
||||
obj.insert("txBytes", QJsonValue(status.txBytes));
|
||||
obj.insert("rxBytes", QJsonValue(status.rxBytes));
|
||||
}
|
||||
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
}
|
37
client/platforms/macos/daemon/macosdaemon.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/* 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 MACOSDAEMON_H
|
||||
#define MACOSDAEMON_H
|
||||
|
||||
#include "daemon.h"
|
||||
#include "dnsutilsmacos.h"
|
||||
#include "iputilsmacos.h"
|
||||
#include "wireguardutilsmacos.h"
|
||||
|
||||
class MacOSDaemon final : public Daemon {
|
||||
friend class IPUtilsMacos;
|
||||
|
||||
public:
|
||||
MacOSDaemon();
|
||||
~MacOSDaemon();
|
||||
|
||||
static MacOSDaemon* instance();
|
||||
|
||||
QByteArray getStatus() override;
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
bool supportDnsUtils() const override { return true; }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
bool supportIPUtils() const override { return true; }
|
||||
IPUtils* iputils() override { return m_iputils; }
|
||||
|
||||
private:
|
||||
WireguardUtilsMacos* m_wgutils = nullptr;
|
||||
DnsUtilsMacos* m_dnsutils = nullptr;
|
||||
IPUtilsMacos* m_iputils = nullptr;
|
||||
};
|
||||
|
||||
#endif // MACOSDAEMON_H
|