WireGuard for MacOS (#248)

* WireGuard for MacOS
* Fix openvpn block-outside-dns
This commit is contained in:
pokamest 2023-07-15 14:19:48 -07:00 committed by GitHub
parent ed5dc7cdfd
commit 35ecb8499d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
118 changed files with 5150 additions and 3486 deletions

View 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 "dummyapplistprovider.h"
#include "leakdetector.h"
DummyAppListProvider::DummyAppListProvider(QObject* parent)
: AppListProvider(parent) {
MZ_COUNT_CTOR(DummyAppListProvider);
}
DummyAppListProvider::~DummyAppListProvider() {
MZ_COUNT_DTOR(DummyAppListProvider);
}
void DummyAppListProvider::getApplicationList() {
QMap<QString, QString> appList;
appList["com.example.one"] = "a Example App 1";
appList["com.example.two"] = "B Example App 2";
appList["org.example.one"] = "c Example App 3";
appList["org.example.two"] = "D Example App 4";
appList["com.example.a"] = "e Example App 5";
appList["com.example.b"] = "F Example App 6";
appList["org.example.c"] = "g Example App 7";
appList["org.example.d"] = "H Example App 8";
emit newAppList(appList);
}

View file

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DUMMYAPPLISTPROVIDER_H
#define DUMMYAPPLISTPROVIDER_H
#include <QObject>
#include "applistprovider.h"
class DummyAppListProvider : public AppListProvider {
Q_OBJECT
public:
DummyAppListProvider(QObject* parent);
~DummyAppListProvider();
void getApplicationList() override;
};
#endif // DUMMYAPPLISTPROVIDER_H

View file

@ -0,0 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "dummynetworkwatcher.h"
#include "leakdetector.h"
DummyNetworkWatcher::DummyNetworkWatcher(QObject* parent)
: NetworkWatcherImpl(parent) {
MZ_COUNT_CTOR(DummyNetworkWatcher);
}
DummyNetworkWatcher::~DummyNetworkWatcher() {
MZ_COUNT_DTOR(DummyNetworkWatcher);
}
void DummyNetworkWatcher::initialize() {}

View file

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DUMMYNETWORKWATCHER_H
#define DUMMYNETWORKWATCHER_H
#include "networkwatcherimpl.h"
class DummyNetworkWatcher final : public NetworkWatcherImpl {
public:
DummyNetworkWatcher(QObject* parent);
~DummyNetworkWatcher();
void initialize() override;
NetworkWatcherImpl::TransportType getTransportType() override {
return TransportType_Other;
};
};
#endif // DUMMYNETWORKWATCHER_H

View file

@ -0,0 +1,25 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "dummypingsender.h"
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger("DummyPingSender");
}
DummyPingSender::DummyPingSender(const QHostAddress& source, QObject* parent)
: PingSender(parent) {
MZ_COUNT_CTOR(DummyPingSender);
Q_UNUSED(source);
}
DummyPingSender::~DummyPingSender() { MZ_COUNT_DTOR(DummyPingSender); }
void DummyPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
logger.debug() << "Dummy ping to:" << dest.toString();
emit recvPing(sequence);
}

View file

@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DUMMYPINGSENDER_H
#define DUMMYPINGSENDER_H
#include "pingsender.h"
class DummyPingSender final : public PingSender {
Q_OBJECT
Q_DISABLE_COPY_MOVE(DummyPingSender)
public:
DummyPingSender(const QHostAddress& source, QObject* parent = nullptr);
~DummyPingSender();
void sendPing(const QHostAddress& dest, quint16 sequence) override;
};
#endif // DUMMYPINGSENDER_H

View file

@ -1,127 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef BIGINT_H
#define BIGINT_H
#include <QVector>
// This BigInt implementation is meant to be used for IPv6 addresses. It
// doesn't support dynamic resize: when the max size is reached, the value
// overflows. If you need to change the size, use `resize()`.
class BigInt final {
public:
explicit BigInt(uint8_t bytes) {
m_value.resize(bytes);
memset(m_value.data(), 0, bytes);
}
BigInt(const BigInt& other) { m_value = other.m_value; }
const uint8_t* value() const { return m_value.data(); }
uint8_t size() const { return m_value.size(); }
// Assign operator.
BigInt& operator=(const BigInt& other) {
m_value = other.m_value;
return *this;
}
// Comparison operators.
bool operator==(const BigInt& other) const {
return m_value == other.m_value;
}
bool operator!=(const BigInt& other) const { return !(*this == other); }
bool operator<(const BigInt& other) const { return cmp(other) < 0; }
bool operator>(const BigInt& other) const { return cmp(other) > 0; }
bool operator<=(const BigInt& other) const { return cmp(other) <= 0; }
bool operator>=(const BigInt& other) const { return cmp(other) >= 0; }
// math operators (only some of them are implemented)
BigInt& operator++() {
for (int i = size() - 1; i >= 0; --i) {
if (m_value[i] < UINT8_MAX) {
++m_value[i];
return *this;
}
m_value[i] = 0;
}
// overflow
memset(m_value.data(), 0, size());
return *this;
}
BigInt& operator+=(const BigInt& other) {
Q_ASSERT(other.size() == size());
uint8_t carry = 0;
for (int i = m_value.size() - 1; i >= 0; --i) {
uint16_t total = carry + m_value[i] + other.m_value[i];
m_value[i] = (uint8_t)(total & UINT8_MAX);
carry = (uint8_t)((total & 0xFF00) >> 8);
}
return *this;
}
// Shift operators
BigInt operator>>(int shift) {
BigInt x(size());
x = *this;
for (int i = 0; i < shift; i++) {
BigInt a(size());
a = x;
a.m_value[size() - 1] = x.m_value[size() - 1] >> 1;
for (int j = size() - 2; j >= 0; j--) {
a.m_value[j] = x.m_value[j] >> 1;
if ((x.m_value[j] & 1) != 0) {
a.m_value[j + 1] |= 128; // Set most significant bit or a uint8_t
}
}
x = a;
}
return x;
}
void setValueAt(uint8_t value, uint8_t pos) {
Q_ASSERT(pos < size());
m_value[pos] = value;
}
uint8_t valueAt(uint8_t pos) const {
Q_ASSERT(size() > pos);
return m_value[pos];
}
private:
int cmp(const BigInt& other) const {
Q_ASSERT(size() == other.size());
for (int i = 0; i < size(); i++) {
int diff = (m_value[i] - other.m_value[i]);
if (diff != 0) return diff;
}
return 0;
}
private:
QVector<uint8_t> m_value;
};
#endif // BIGINT_H

View file

@ -1,86 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef BIGINTIPV6ADDR_H
#define BIGINTIPV6ADDR_H
#include "bigint.h"
#include <QHostAddress>
class BigIntIPv6Addr final {
public:
BigIntIPv6Addr() : m_value(16) {}
explicit BigIntIPv6Addr(const Q_IPV6ADDR& a) : m_value(16) {
for (int i = 0; i < 16; ++i) m_value.setValueAt(a[i], i);
}
BigIntIPv6Addr(const BigIntIPv6Addr& other) : m_value(16) { *this = other; }
Q_IPV6ADDR value() const {
Q_IPV6ADDR addr;
for (int i = 0; i < 16; ++i) addr[i] = m_value.valueAt(i);
return addr;
}
// Assign operator.
BigIntIPv6Addr& operator=(const BigIntIPv6Addr& other) {
m_value = other.m_value;
return *this;
}
// Comparison operators.
bool operator==(const BigIntIPv6Addr& other) const {
return m_value == other.m_value;
}
bool operator!=(const BigIntIPv6Addr& other) const {
return m_value != other.m_value;
}
bool operator<(const BigIntIPv6Addr& other) const {
return m_value < other.m_value;
}
bool operator>(const BigIntIPv6Addr& other) const {
return m_value > other.m_value;
}
bool operator<=(const BigIntIPv6Addr& other) const {
return m_value <= other.m_value;
}
bool operator>=(const BigIntIPv6Addr& other) const {
return m_value >= other.m_value;
}
// math operators (only some of them are implemented)
BigIntIPv6Addr& operator++() {
++m_value;
return *this;
}
BigIntIPv6Addr& operator+=(const BigIntIPv6Addr& b) {
m_value += b.m_value;
return *this;
}
// Shift operators
BigIntIPv6Addr operator>>(int shift) {
BigIntIPv6Addr x;
x.m_value = m_value >> shift;
return x;
}
private:
BigInt m_value;
};
#endif // BIGINTIPV6ADDR_H

View file

@ -1,16 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IOSADJUSTHELPER_H
#define IOSADJUSTHELPER_H
#include <QString>
class IOSAdjustHelper final {
public:
static void initialize();
static void trackEvent(const QString& eventToken);
};
#endif // IOSADJUSTHELPER_H

View file

@ -1,37 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "iosadjusthelper.h"
#include "logger.h"
#include "constants.h"
#import <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];
}
}

View file

@ -1,23 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IOSAUTHENTICATIONLISTENER_H
#define IOSAUTHENTICATIONLISTENER_H
#include "authenticationlistener.h"
#include <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

View file

@ -1,139 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "iosauthenticationlistener.h"
#include "leakdetector.h"
#include "logger.h"
#include "mozillavpn.h"
#include "qmlengineholder.h"
#include <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);
}
}

View file

@ -1,39 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IOSCONTROLLER_H
#define IOSCONTROLLER_H
#include "vpnprotocol.h"
#include <QObject>
class IOSVPNProtocol final : public VpnProtocol {
Q_DISABLE_COPY_MOVE(IOSVPNProtocol)
public:
IOSController();
~IOSController();
void initialize(const Device* device, const Keys* keys) override;
void activate(const QList<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

View file

@ -1,240 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ioscontroller.h"
#include "Mozilla_VPN-Swift.h"
#include "device.h"
#include "ipaddressrange.h"
#include "keys.h"
#include "leakdetector.h"
#include "logger.h"
#include "mozillavpn.h"
#include "server.h"
#include "settingsholder.h"
#include <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();
}

View file

@ -1,288 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
import NetworkExtension
let vpnName = "Mozilla VPN"
var vpnBundleID = "";
@objc class VPNIPAddressRange : NSObject {
public var address: NSString = ""
public var networkPrefixLength: UInt8 = 0
public var isIpv6: Bool = false
@objc init(address: NSString, networkPrefixLength: UInt8, isIpv6: Bool) {
super.init()
self.address = address
self.networkPrefixLength = networkPrefixLength
self.isIpv6 = isIpv6
}
}
public class IOSControllerImpl : NSObject {
private var tunnel: NETunnelProviderManager? = nil
private var stateChangeCallback: ((Bool) -> Void?)? = nil
private var privateKey : PrivateKey? = nil
private var deviceIpv4Address: String? = nil
private var deviceIpv6Address: String? = nil
@objc enum ConnectionState: Int { case Error, Connected, Disconnected }
@objc init(bundleID: String, privateKey: Data, deviceIpv4Address: String, deviceIpv6Address: String, closure: @escaping (ConnectionState, Date?) -> Void, callback: @escaping (Bool) -> Void) {
super.init()
Logger.configureGlobal(tagged: "APP", withFilePath: "")
vpnBundleID = bundleID;
precondition(!vpnBundleID.isEmpty)
stateChangeCallback = callback
self.privateKey = PrivateKey(rawValue: privateKey)
self.deviceIpv4Address = deviceIpv4Address
self.deviceIpv6Address = deviceIpv6Address
NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil)
NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in
if let error = error {
Logger.global?.log(message: "Loading from preference failed: \(error)")
closure(ConnectionState.Error, nil)
return
}
if self == nil {
Logger.global?.log(message: "We are shutting down.")
return
}
let nsManagers = managers ?? []
Logger.global?.log(message: "We have received \(nsManagers.count) managers.")
let tunnel = nsManagers.first(where: IOSControllerImpl.isOurManager(_:))
if tunnel == nil {
Logger.global?.log(message: "Creating the tunnel")
self!.tunnel = NETunnelProviderManager()
closure(ConnectionState.Disconnected, nil)
return
}
Logger.global?.log(message: "Tunnel already exists")
self!.tunnel = tunnel
if tunnel?.connection.status == .connected {
closure(ConnectionState.Connected, tunnel?.connection.connectedDate)
} else {
closure(ConnectionState.Disconnected, nil)
}
}
}
@objc private func vpnStatusDidChange(notification: Notification) {
guard let session = (notification.object as? NETunnelProviderSession), tunnel?.connection == session else { return }
switch session.status {
case .connected:
Logger.global?.log(message: "STATE CHANGED: connected")
case .connecting:
Logger.global?.log(message: "STATE CHANGED: connecting")
case .disconnected:
Logger.global?.log(message: "STATE CHANGED: disconnected")
case .disconnecting:
Logger.global?.log(message: "STATE CHANGED: disconnecting")
case .invalid:
Logger.global?.log(message: "STATE CHANGED: invalid")
case .reasserting:
Logger.global?.log(message: "STATE CHANGED: reasserting")
default:
Logger.global?.log(message: "STATE CHANGED: unknown status")
}
// We care about "unknown" state changes.
if (session.status != .connected && session.status != .disconnected) {
return
}
stateChangeCallback?(session.status == .connected)
}
private static func isOurManager(_ manager: NETunnelProviderManager) -> Bool {
guard
let proto = manager.protocolConfiguration,
let tunnelProto = proto as? NETunnelProviderProtocol
else {
Logger.global?.log(message: "Ignoring manager because the proto is invalid.")
return false
}
if (tunnelProto.providerBundleIdentifier == nil) {
Logger.global?.log(message: "Ignoring manager because the bundle identifier is null.")
return false
}
if (tunnelProto.providerBundleIdentifier != vpnBundleID) {
Logger.global?.log(message: "Ignoring manager because the bundle identifier doesn't match.")
return false;
}
Logger.global?.log(message: "Found the manager with the correct bundle identifier: \(tunnelProto.providerBundleIdentifier!)")
return true
}
@objc func connect(dnsServer: String, serverIpv6Gateway: String, serverPublicKey: String, serverIpv4AddrIn: String, serverPort: Int, allowedIPAddressRanges: Array<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) {
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("", "", "")
}
}
}

View file

@ -1,15 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IOSDATAMIGRATION_H
#define IOSDATAMIGRATION_H
#include <QString>
class IOSDataMigration final {
public:
static void migrate();
};
#endif // IOSDATAMIGRATION_H

View file

@ -1,172 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "iosdatamigration.h"
#include "device.h"
#include "logger.h"
#include "mozillavpn.h"
#include "user.h"
#include <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();
}

View file

@ -1,369 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "platforms/ios/iosiaphandler.h"
#include "constants.h"
#include "iosutils.h"
#include "leakdetector.h"
#include "logger.h"
#include "mozillavpn.h"
#include "networkrequest.h"
#include "settingsholder.h"
#include <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 unknown state";
break;
}
}
if (!completedTransactions && !canceledTransactions && !failedTransactions) {
// Nothing completed, nothing restored, nothing failed. Just purchasing transactions.
return;
}
if (canceledTransactions) {
logger.debug() << "Subscription canceled";
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection);
} else if (failedTransactions) {
logger.error() << "Subscription failed";
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection);
} else if (completedTransactionIds.isEmpty()) {
Q_ASSERT(completedTransactions);
logger.debug() << "Subscription completed - but all the transactions are known";
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection);
} else if (AmneziaVPN::instance()->userAuthenticated()) {
Q_ASSERT(completedTransactions);
logger.debug() << "Subscription completed. Let's start the validation";
QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection,
Q_ARG(QStringList, completedTransactionIds));
} else {
Q_ASSERT(completedTransactions);
logger.debug() << "Subscription completed - but the user is not authenticated yet";
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection);
}
for (SKPaymentTransaction* transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStateFailed:
[[fallthrough]];
case SKPaymentTransactionStateRestored:
[[fallthrough]];
case SKPaymentTransactionStatePurchased:
[queue finishTransaction:transaction];
break;
default:
break;
}
}
}
- (void)requestDidFinish:(SKRequest*)request {
logger.debug() << "Receipt refreshed correctly";
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList()));
}
- (void)request:(SKRequest*)request didFailWithError:(NSError*)error {
logger.error() << "Failed to refresh the receipt"
<< QString::fromNSString(error.localizedDescription);
QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_handler, "subscriptionFailed", Qt::QueuedConnection);
}
@end
IOSIAPHandler::IOSIAPHandler(QObject* parent) : IAPHandler(parent) {
MVPN_COUNT_CTOR(IOSIAPHandler);
m_delegate = [[IOSIAPHandlerDelegate alloc] initWithObject:this];
[[SKPaymentQueue defaultQueue]
addTransactionObserver:static_cast<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();
});
}

View file

@ -1,230 +0,0 @@
import Foundation
import Darwin
typealias InetFamily = UInt8
typealias Flags = Int32
func destinationAddress(_ data: ifaddrs) -> UnsafeMutablePointer<sockaddr>! { return data.ifa_dstaddr }
func socketLength4(_ addr: sockaddr) -> UInt32 { return socklen_t(addr.sa_len) }
/**
* This class represents a network interface in your system. For example, `en0` with a certain IP address.
* It is a wrapper around the `getifaddrs` system call.
*
* Typical use of this class is to first call `Interface.allInterfaces()` and then use the properties of the interface(s) that you need.
*
* - See: `/usr/include/ifaddrs.h`
*/
open class Interface : CustomStringConvertible, CustomDebugStringConvertible {
public var id = UUID()
/// The network interface family (IPv4 or IPv6, otherwise error).
public enum Family : Int {
case ipv4
case ipv6
case other
public func toString() -> String {
switch (self) {
case .ipv4: return "IPv4"
case .ipv6: return "IPv6"
default: return "other"
}
}
}
/**
* Returns all network interfaces in your system. If you have an interface name (e.g. `en0`) that has
* multiple IP addresses (e.g. one IPv4 address and a few IPv6 addresses), then they will be returned
* as separate instances of Interface.
* - Returns: An array containing all network interfaces in your system.
*/
public static func allInterfaces() -> [Interface] {
var interfaces : [Interface] = []
var ifaddrsPtr : UnsafeMutablePointer<ifaddrs>? = nil
if getifaddrs(&ifaddrsPtr) == 0 {
var ifaddrPtr = ifaddrsPtr
while ifaddrPtr != nil {
let addr = ifaddrPtr?.pointee.ifa_addr.pointee
if addr?.sa_family == InetFamily(AF_INET) || addr?.sa_family == InetFamily(AF_INET6) {
interfaces.append(Interface(data: (ifaddrPtr?.pointee)!))
}
ifaddrPtr = ifaddrPtr?.pointee.ifa_next
}
freeifaddrs(ifaddrsPtr)
}
return interfaces
}
/**
* Returns a new Interface instance that does not represent a real network interface, but can be used for (unit) testing.
* - Returns: An instance of Interface that does *not* represent a real network interface.
*/
public static func createTestDummy(_ name:String, family:Family, address:String, multicastSupported:Bool, broadcastAddress:String?) -> Interface
{
return Interface(name: name, family: family, address: address, netmask: nil, running: true, up: true, loopback: false, multicastSupported: multicastSupported, broadcastAddress: broadcastAddress)
}
/**
* Initialize a new Interface with the given properties.
*/
public init(name:String, family:Family, address:String?, netmask:String?, running:Bool, up:Bool, loopback:Bool, multicastSupported:Bool, broadcastAddress:String?) {
self.name = name
self.family = family
self.address = address
self.netmask = netmask
self.running = running
self.up = up
self.loopback = loopback
self.multicastSupported = multicastSupported
self.broadcastAddress = broadcastAddress
}
convenience init(data:ifaddrs) {
let flags = Flags(data.ifa_flags)
let broadcastValid : Bool = ((flags & IFF_BROADCAST) == IFF_BROADCAST)
self.init(name: String(cString: data.ifa_name),
family: Interface.extractFamily(data),
address: Interface.extractAddress(data.ifa_addr),
netmask: Interface.extractAddress(data.ifa_netmask),
running: ((flags & IFF_RUNNING) == IFF_RUNNING),
up: ((flags & IFF_UP) == IFF_UP),
loopback: ((flags & IFF_LOOPBACK) == IFF_LOOPBACK),
multicastSupported: ((flags & IFF_MULTICAST) == IFF_MULTICAST),
broadcastAddress: ((broadcastValid && destinationAddress(data) != nil) ? Interface.extractAddress(destinationAddress(data)) : nil))
}
fileprivate static func extractFamily(_ data:ifaddrs) -> Family {
var family : Family = .other
let addr = data.ifa_addr.pointee
if addr.sa_family == InetFamily(AF_INET) {
family = .ipv4
}
else if addr.sa_family == InetFamily(AF_INET6) {
family = .ipv6
}
else {
family = .other
}
return family
}
fileprivate static func extractAddress(_ address: UnsafeMutablePointer<sockaddr>?) -> String? {
guard let address = address else { return nil }
return address.withMemoryRebound(to: sockaddr_storage.self, capacity: 1) {
if (address.pointee.sa_family == sa_family_t(AF_INET)) {
return extractAddress_ipv4($0)
}
else if (address.pointee.sa_family == sa_family_t(AF_INET6)) {
return extractAddress_ipv6($0)
}
else {
return nil
}
}
}
fileprivate static func extractAddress_ipv4(_ address:UnsafeMutablePointer<sockaddr_storage>) -> String? {
return address.withMemoryRebound(to: sockaddr.self, capacity: 1) { addr in
var address : String? = nil
var hostname = [CChar](repeating: 0, count: Int(2049))
if (getnameinfo(&addr.pointee, socklen_t(socketLength4(addr.pointee)), &hostname,
socklen_t(hostname.count), nil, socklen_t(0), NI_NUMERICHOST) == 0) {
address = String(cString: hostname)
}
else {
// var error = String.fromCString(gai_strerror(errno))!
// println("ERROR: \(error)")
}
return address
}
}
fileprivate static func extractAddress_ipv6(_ address:UnsafeMutablePointer<sockaddr_storage>) -> String? {
var addr = address.pointee
var ip : [Int8] = [Int8](repeating: Int8(0), count: Int(INET6_ADDRSTRLEN))
return inetNtoP(&addr, ip: &ip)
}
fileprivate static func inetNtoP(_ addr:UnsafeMutablePointer<sockaddr_storage>, ip:UnsafeMutablePointer<Int8>) -> String? {
return addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { addr6 in
let conversion:UnsafePointer<CChar> = inet_ntop(AF_INET6, &addr6.pointee.sin6_addr, ip, socklen_t(INET6_ADDRSTRLEN))
return String(cString: conversion)
}
}
/**
* Creates the network format representation of the interface's IP address. Wraps `inet_pton`.
*/
open var addressBytes: [UInt8]? {
guard let addr = address else { return nil }
let af:Int32
let len:Int
switch family {
case .ipv4:
af = AF_INET
len = 4
case .ipv6:
af = AF_INET6
len = 16
default:
return nil
}
var bytes = [UInt8](repeating: 0, count: len)
let result = inet_pton(af, addr, &bytes)
return ( result == 1 ) ? bytes : nil
}
/// `IFF_RUNNING` flag of `ifaddrs->ifa_flags`.
open var isRunning: Bool { return running }
/// `IFF_UP` flag of `ifaddrs->ifa_flags`.
open var isUp: Bool { return up }
/// `IFF_LOOPBACK` flag of `ifaddrs->ifa_flags`.
open var isLoopback: Bool { return loopback }
/// `IFF_MULTICAST` flag of `ifaddrs->ifa_flags`.
open var supportsMulticast: Bool { return multicastSupported }
/// Field `ifaddrs->ifa_name`.
public let name : String
/// Field `ifaddrs->ifa_addr->sa_family`.
public let family : Family
/// Extracted from `ifaddrs->ifa_addr`, supports both IPv4 and IPv6.
public let address : String?
/// Extracted from `ifaddrs->ifa_netmask`, supports both IPv4 and IPv6.
public let netmask : String?
/// Extracted from `ifaddrs->ifa_dstaddr`. Not applicable for IPv6.
public let broadcastAddress : String?
fileprivate let running : Bool
fileprivate let up : Bool
fileprivate let loopback : Bool
fileprivate let multicastSupported : Bool
/// Returns the interface name.
open var description: String { return name }
/// Returns a string containing a few properties of the Interface.
open var debugDescription: String {
var s = "Interface name:\(name) family:\(family)"
if let ip = address {
s += " ip:\(ip)"
}
s += isUp ? " (up)" : " (down)"
s += isRunning ? " (running)" : "(not running)"
return s
}
}
#if swift(>=5)
extension Interface: Identifiable {}
#endif

View file

@ -0,0 +1,32 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IOSNETWORKWATCHER_H
#define IOSNETWORKWATCHER_H
#include <Network/Network.h>
#include "networkwatcherimpl.h"
class IOSNetworkWatcher : public NetworkWatcherImpl {
public:
explicit IOSNetworkWatcher(QObject* parent);
~IOSNetworkWatcher();
void initialize() override;
NetworkWatcherImpl::TransportType getTransportType() override;
private:
NetworkWatcherImpl::TransportType toTransportType(nw_path_t path);
void controllerStateChanged();
NetworkWatcherImpl::TransportType m_currentDefaultTransport =
NetworkWatcherImpl::TransportType_Unknown;
NetworkWatcherImpl::TransportType m_currentVPNTransport =
NetworkWatcherImpl::TransportType_Unknown;
nw_path_monitor_t m_networkMonitor = nil;
nw_connection_t m_observableConnection = nil;
};
#endif // IOSNETWORKWATCHER_H

View file

@ -0,0 +1,79 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "iosnetworkwatcher.h"
#include "leakdetector.h"
#include "logger.h"
#import <Network/Network.h>
namespace {
Logger logger("IOSNetworkWatcher");
dispatch_queue_t s_queue = dispatch_queue_create("VPNNetwork.queue", DISPATCH_QUEUE_SERIAL);
}
IOSNetworkWatcher::IOSNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) {
MZ_COUNT_CTOR(IOSNetworkWatcher);
}
IOSNetworkWatcher::~IOSNetworkWatcher() {
MZ_COUNT_DTOR(IOSNetworkWatcher);
if (m_networkMonitor != nil) {
nw_path_monitor_cancel(m_networkMonitor);
nw_release(m_networkMonitor);
}
}
void IOSNetworkWatcher::initialize() {
m_networkMonitor = nw_path_monitor_create();
nw_path_monitor_set_queue(m_networkMonitor, s_queue);
nw_path_monitor_set_update_handler(m_networkMonitor, ^(nw_path_t _Nonnull path) {
m_currentDefaultTransport = toTransportType(path);
});
nw_path_monitor_start(m_networkMonitor);
//TODO IMPL FOR AMNEZIA
}
NetworkWatcherImpl::TransportType IOSNetworkWatcher::getTransportType() {
//TODO IMPL FOR AMNEZIA
if (m_observableConnection != nil) {
return m_currentVPNTransport;
}
// If we don't have an open tunnel-observer, m_currentVPNTransport is probably wrong.
return NetworkWatcherImpl::TransportType_Unknown;
}
NetworkWatcherImpl::TransportType IOSNetworkWatcher::toTransportType(nw_path_t path) {
if (path == nil) {
return NetworkWatcherImpl::TransportType_Unknown;
}
auto status = nw_path_get_status(path);
if (status != nw_path_status_satisfied && status != nw_path_status_satisfiable) {
// We're offline.
return NetworkWatcherImpl::TransportType_None;
}
if (nw_path_uses_interface_type(path, nw_interface_type_wifi)) {
return NetworkWatcherImpl::TransportType_WiFi;
}
if (nw_path_uses_interface_type(path, nw_interface_type_wired)) {
return NetworkWatcherImpl::TransportType_Ethernet;
}
if (nw_path_uses_interface_type(path, nw_interface_type_cellular)) {
return NetworkWatcherImpl::TransportType_Cellular;
}
if (nw_path_uses_interface_type(path, nw_interface_type_other)) {
return NetworkWatcherImpl::TransportType_Other;
}
if (nw_path_uses_interface_type(path, nw_interface_type_loopback)) {
return NetworkWatcherImpl::TransportType_Other;
}
return NetworkWatcherImpl::TransportType_Unknown;
}
void IOSNetworkWatcher::controllerStateChanged() {
//TODO IMPL FOR AMNEZIA
}

View file

@ -73,7 +73,7 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"mozillavpn"
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
content:content
trigger:trigger];

View file

@ -1,21 +0,0 @@
#ifndef iosopenvpn2ssadapter_h
#define iosopenvpn2ssadapter_h
#import <Foundation/Foundation.h>
#import "ssadapterpacketflow.h"
#import <OpenVPNAdapter/OpenVPNAdapterPacketFlow.h>
@interface ShadowSocksAdapterFlowBridge: NSObject
@property (nonatomic, weak) id<ShadowSocksAdapterPacketFlow> ssPacketFlow;
@property (nonatomic, readonly) CFSocketRef ssSocket;
@property (nonatomic, readonly) CFSocketRef packetFlowSocket;
- (BOOL)configureSocketWithError:(NSError **)error;
- (void)invalidateSocketsIfNeeded;
- (void)processPackets;
@end
#endif /* iosopenvpn2ssadapter_h */

View file

@ -1,139 +0,0 @@
#import "iosopenvpn2ssadapter.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#import "sspacket.h"
#import "ssadapterpacketflow.h"
@implementation ShadowSocksAdapterFlowBridge
# pragma mark - Sockets Configuration
static void SocketCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *obj) {
if (type != kCFSocketDataCallBack) {
return;
}
SSPacket *packet = [[SSPacket alloc] initWithSSData:data];
ShadowSocksAdapterFlowBridge *bridge = (__bridge ShadowSocksAdapterFlowBridge*)obj;
[bridge writePackets:@[packet] toPacketFlow:bridge.ssPacketFlow];
}
- (BOOL)configureSocketWithError:(NSError * __autoreleasing *)error {
int sockets[2];
if (socketpair(PF_LOCAL, SOCK_DGRAM, IPPROTO_IP, sockets) == -1) {
if (error) {
NSDictionary *userInfo = @{
// TODO: handle
};
*error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo];
}
return NO;
}
CFSocketContext socketCtx = {0, (__bridge void *)self, NULL, NULL, NULL};
_packetFlowSocket = CFSocketCreateWithNative(kCFAllocatorDefault, sockets[0], kCFSocketDataCallBack, SocketCallback, &socketCtx);
_ssSocket = CFSocketCreateWithNative(kCFAllocatorDefault, sockets[1], kCFSocketNoCallBack, NULL, NULL);
if (!(_packetFlowSocket && _ssSocket)) {
if (error) {
NSDictionary *userInfo = @{
// TODO: handle
};
*error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo];
}
return NO;
}
if (!([self configureOptionsForSocket:_packetFlowSocket error:error] && [self configureOptionsForSocket:_ssSocket error:error])) {
return NO;
}
CFRunLoopSourceRef packetFlowSocketSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _packetFlowSocket, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), packetFlowSocketSource, kCFRunLoopDefaultMode);
CFRelease(packetFlowSocketSource);
return YES;
}
- (void)invalidateSocketsIfNeeded {
if (_ssSocket) {
CFSocketInvalidate(_ssSocket);
CFRelease(_ssSocket);
_ssSocket = NULL;
}
if (_packetFlowSocket) {
CFSocketInvalidate(_packetFlowSocket);
CFRelease(_packetFlowSocket);
_packetFlowSocket = NULL;
}
}
- (void)processPackets {
NSAssert(self.ssPacketFlow != nil, @"packetFlow property shouldn't be nil, set it before start reading packets.");
__weak typeof(self) weakSelf = self;
[self.ssPacketFlow readPacketsWithCompletionHandler:^(NSArray<NSData *> *packets, NSArray<NSNumber *> *protocols) {
__strong typeof(self) self = weakSelf;
[self writePackets:packets protocols:protocols toSocket:self.packetFlowSocket];
[self processPackets];
}];
}
- (void)dealloc {
[self invalidateSocketsIfNeeded];
[super dealloc];
}
# pragma mark - Socket configuration
- (BOOL)configureOptionsForSocket:(CFSocketRef)socket error:(NSError * __autoreleasing *)error {
CFSocketNativeHandle socketHandle = CFSocketGetNative(socket);
int buf_value = 65536;
socklen_t buf_len = sizeof(buf_value);
if (setsockopt(socketHandle, SOL_SOCKET, SO_RCVBUF, &buf_value, buf_len) == -1) {
if (error) {
NSDictionary *userInfo = @{
// TODO: handle
};
*error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo];
}
return NO;
}
if (setsockopt(socketHandle, SOL_SOCKET, SO_SNDBUF, &buf_value, buf_len) == -1) {
if (error) {
NSDictionary *userInfo = @{
// TODO: handle
};
*error = [NSError errorWithDomain:@"Some domain" code:100 userInfo:userInfo];
}
return NO;
}
return YES;
}
# pragma mark - Protocol methods
- (void)writePackets:(NSArray<SSPacket *> *)packets toPacketFlow:(id<ShadowSocksAdapterPacketFlow>)packetFlow {
NSAssert(self.ssPacketFlow != nil, @"packetFlow shouldn't be nil, check provided parameter before start writing packets.");
NSMutableArray<NSData *> *flowPackets = [[NSMutableArray alloc] init];
NSMutableArray<NSNumber *> *protocols = [[NSMutableArray alloc] init];
[packets enumerateObjectsUsingBlock:^(SSPacket * _Nonnull packet, NSUInteger idx, BOOL * _Nonnull stop) {
[flowPackets addObject:packet.ssPacketFlowData];
[protocols addObject:packet.protocolFamily];
}];
[packetFlow writePackets:flowPackets withProtocols:protocols];
}
- (void)writePackets:(NSArray<NSData *> *)packets protocols:(NSArray<NSNumber *> *)protocols toSocket:(CFSocketRef)socket {
if (socket == NULL) { return; }
[packets enumerateObjectsUsingBlock:^(NSData *data, NSUInteger idx, BOOL *stop) {
NSNumber *protocolFamily = protocols[idx];
SSPacket *packet = [[SSPacket alloc] initWithPacketFlowData:data protocolFamily:protocolFamily];
CFSocketSendData(socket, NULL, (CFDataRef)packet.vpnData, 0.05);
}];
}
@end

View file

@ -1,17 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IOSUTILS_H
#define IOSUTILS_H
#include <QString>
class IOSUtils final {
public:
static QString computerName();
static QString IAPReceipt();
};
#endif // IOSUTILS_H

View file

@ -1,63 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "iosutils.h"
#include "logger.h"
#include <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);
}

View file

@ -1,313 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ipaddress.h"
#include "bigintipv6addr.h"
#include <QtMath>
namespace {
quint32 s_allIpV4Ones = static_cast<quint32>(qPow(2, 32) - 1);
BigIntIPv6Addr s_allIPv6Ones;
bool s_ipv6Initialized = false;
void maybeInitialize() {
if (s_ipv6Initialized) return;
s_ipv6Initialized = true;
Q_IPV6ADDR allOnes;
memset((void*)&allOnes, static_cast<quint8>(qPow(2, 8) - 1), sizeof(allOnes));
s_allIPv6Ones = BigIntIPv6Addr(allOnes);
}
} // namespace
// static
IPAddress IPAddress::create(const QString& ip) {
if (ip.contains("/")) {
QPair<QHostAddress, int> p = QHostAddress::parseSubnet(ip);
if (p.first.protocol() == QAbstractSocket::IPv4Protocol) {
if (p.second < 32) {
return IPAddress(p.first, p.second);
}
return IPAddress(p.first);
}
if (p.first.protocol() == QAbstractSocket::IPv6Protocol) {
if (p.second < 128) {
return IPAddress(p.first, p.second);
}
return IPAddress(p.first);
}
Q_ASSERT(false);
}
return IPAddress(QHostAddress(ip));
}
IPAddress::IPAddress() {
maybeInitialize();
}
IPAddress::IPAddress(const IPAddress& other) {
maybeInitialize();
*this = other;
}
IPAddress& IPAddress::operator=(const IPAddress& other) {
if (this == &other) return *this;
m_address = other.m_address;
m_prefixLength = other.m_prefixLength;
m_netmask = other.m_netmask;
m_hostmask = other.m_hostmask;
m_broadcastAddress = other.m_broadcastAddress;
return *this;
}
IPAddress::IPAddress(const QHostAddress& address)
: m_address(address), m_broadcastAddress(address) {
maybeInitialize();
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
m_prefixLength = 32;
m_netmask = QHostAddress(s_allIpV4Ones);
m_hostmask = QHostAddress((quint32)(0));
} else {
Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
m_prefixLength = 128;
m_netmask = QHostAddress(s_allIPv6Ones.value());
{
Q_IPV6ADDR ipv6;
memset((void*)&ipv6, 0, sizeof(ipv6));
m_hostmask = QHostAddress(ipv6);
}
}
}
IPAddress::IPAddress(const QHostAddress& address, int prefixLength)
: m_address(address), m_prefixLength(prefixLength) {
maybeInitialize();
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
Q_ASSERT(prefixLength >= 0 && prefixLength <= 32);
m_netmask = QHostAddress(s_allIpV4Ones ^ (s_allIpV4Ones >> prefixLength));
m_hostmask = QHostAddress(m_netmask.toIPv4Address() ^ s_allIpV4Ones);
m_broadcastAddress =
QHostAddress(address.toIPv4Address() | m_hostmask.toIPv4Address());
} else {
Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
Q_ASSERT(prefixLength >= 0 && prefixLength <= 128);
Q_IPV6ADDR netmask;
{
BigIntIPv6Addr tmp = (s_allIPv6Ones >> prefixLength);
for (int i = 0; i < 16; ++i)
netmask[i] = s_allIPv6Ones.value()[i] ^ tmp.value()[i];
}
m_netmask = QHostAddress(netmask);
{
Q_IPV6ADDR tmp;
for (int i = 0; i < 16; ++i)
tmp[i] = netmask[i] ^ s_allIPv6Ones.value()[i];
m_hostmask = QHostAddress(tmp);
}
{
Q_IPV6ADDR ipv6Address = address.toIPv6Address();
Q_IPV6ADDR ipv6Hostname = m_hostmask.toIPv6Address();
for (int i = 0; i < 16; ++i) ipv6Address[i] |= ipv6Hostname[i];
m_broadcastAddress = QHostAddress(ipv6Address);
}
}
}
IPAddress::~IPAddress() { }
QAbstractSocket::NetworkLayerProtocol IPAddress::type() const {
return m_address.protocol();
}
bool IPAddress::overlaps(const IPAddress& other) const {
return other.contains(m_address) || other.contains(m_broadcastAddress) ||
contains(other.m_address) || contains(other.m_broadcastAddress);
}
bool IPAddress::contains(const QHostAddress& address) const {
if (address.protocol() != m_address.protocol()) {
return false;
}
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
return (m_address.toIPv4Address() <= address.toIPv4Address()) &&
(address.toIPv4Address() <= m_broadcastAddress.toIPv4Address());
}
Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
return (BigIntIPv6Addr(m_address.toIPv6Address()) <=
BigIntIPv6Addr(address.toIPv6Address())) &&
(BigIntIPv6Addr(address.toIPv6Address()) <=
BigIntIPv6Addr(m_broadcastAddress.toIPv6Address()));
}
bool IPAddress::operator==(const IPAddress& other) const {
return m_address == other.m_address && m_netmask == other.m_netmask;
}
bool IPAddress::subnetOf(const IPAddress& other) const {
if (other.m_address.protocol() != m_address.protocol()) {
return false;
}
if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
return other.m_address.toIPv4Address() <= m_address.toIPv4Address() &&
other.m_broadcastAddress.toIPv4Address() >=
m_broadcastAddress.toIPv4Address();
}
Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol);
return BigIntIPv6Addr(other.m_address.toIPv6Address()) <=
BigIntIPv6Addr(m_address.toIPv6Address()) &&
BigIntIPv6Addr(other.m_broadcastAddress.toIPv6Address()) >=
BigIntIPv6Addr(m_broadcastAddress.toIPv6Address());
}
QList<IPAddress> IPAddress::subnets() const {
QList<IPAddress> list;
if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
if (m_prefixLength == 32) {
list.append(*this);
return list;
}
quint64 start = m_address.toIPv4Address();
quint64 end = quint64(m_broadcastAddress.toIPv4Address()) + 1;
quint64 step = ((quint64)m_hostmask.toIPv4Address() + 1) >> 1;
while (start < end) {
int newPrefixLength = m_prefixLength + 1;
if (newPrefixLength == 32) {
list.append(IPAddress(QHostAddress(static_cast<quint32>(start))));
} else {
list.append(IPAddress(QHostAddress(static_cast<quint32>(start)),
m_prefixLength + 1));
}
start += step;
}
return list;
}
Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol);
if (m_prefixLength == 128) {
list.append(*this);
return list;
}
BigInt start(17);
{
Q_IPV6ADDR addr = m_address.toIPv6Address();
for (int i = 0; i < 16; ++i) {
start.setValueAt(addr[i], i + 1);
}
}
BigInt end(17);
{
Q_IPV6ADDR addr = m_broadcastAddress.toIPv6Address();
for (int i = 0; i < 16; ++i) {
end.setValueAt(addr[i], i + 1);
}
++end;
}
BigInt step(17);
{
Q_IPV6ADDR addr = m_hostmask.toIPv6Address();
for (int i = 0; i < 16; ++i) {
step.setValueAt(addr[i], i + 1);
}
step = (++step) >> 1;
}
while (start < end) {
int newPrefixLength = m_prefixLength + 1;
Q_IPV6ADDR startIPv6;
for (int i = 0; i < 16; ++i) {
startIPv6[i] = start.valueAt(i + 1);
}
if (newPrefixLength == 128) {
list.append(IPAddress(QHostAddress(startIPv6)));
} else {
list.append(IPAddress(QHostAddress(startIPv6), m_prefixLength + 1));
}
start += step;
}
return list;
}
// static
QList<IPAddress> IPAddress::excludeAddresses(
const QList<IPAddress>& sourceList, const QList<IPAddress>& excludeList) {
QList<IPAddress> results = sourceList;
for (const IPAddress& exclude : excludeList) {
QList<IPAddress> newResults;
for (const IPAddress& ip : results) {
if (ip.overlaps(exclude)) {
QList<IPAddress> range = ip.excludeAddresses(exclude);
newResults.append(range);
} else {
newResults.append(ip);
}
}
results = newResults;
}
return results;
}
QList<IPAddress> IPAddress::excludeAddresses(const IPAddress& ip) const {
QList<IPAddress> sn = subnets();
Q_ASSERT(sn.length() >= 2);
QList<IPAddress> result;
while (sn[0] != ip && sn[1] != ip) {
if (ip.subnetOf(sn[0])) {
result.append(sn[1]);
sn = sn[0].subnets();
} else if (ip.subnetOf(sn[1])) {
result.append(sn[0]);
sn = sn[1].subnets();
} else {
Q_ASSERT(false);
}
}
if (sn[0] == ip) {
result.append(sn[1]);
} else if (sn[1] == ip) {
result.append(sn[0]);
} else {
Q_ASSERT(false);
}
return result;
}

View file

@ -1,59 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IPADDRESS_H
#define IPADDRESS_H
#include <QHostAddress>
class IPAddress final {
public:
static IPAddress create(const QString& ip);
static QList<IPAddress> excludeAddresses(const QList<IPAddress>& sourceList,
const QList<IPAddress>& excludeList);
IPAddress();
IPAddress(const IPAddress& other);
IPAddress& operator=(const IPAddress& other);
~IPAddress();
QString toString() const {
return QString("%1/%2").arg(m_address.toString()).arg(m_prefixLength);
}
const QHostAddress& address() const { return m_address; }
int prefixLength() const { return m_prefixLength; }
const QHostAddress& netmask() const { return m_netmask; }
const QHostAddress& hostmask() const { return m_hostmask; }
const QHostAddress& broadcastAddress() const { return m_broadcastAddress; }
bool overlaps(const IPAddress& other) const;
bool contains(const QHostAddress& address) const;
bool operator==(const IPAddress& other) const;
bool operator!=(const IPAddress& other) const { return !operator==(other); }
bool subnetOf(const IPAddress& other) const;
QList<IPAddress> subnets() const;
QList<IPAddress> excludeAddresses(const IPAddress& ip) const;
QAbstractSocket::NetworkLayerProtocol type() const;
private:
IPAddress(const QHostAddress& address);
IPAddress(const QHostAddress& address, int prefixLength);
private:
QHostAddress m_address;
int m_prefixLength;
QHostAddress m_netmask;
QHostAddress m_hostmask;
QHostAddress m_broadcastAddress;
};
#endif // IPADDRESS_H

View file

@ -1,75 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "leakdetector.h"
#include <QHash>
#include <QMutex>
#include <QObject>
#include <QTextStream>
#ifdef MVPN_DEBUG
static QMutex s_leakDetector;
QHash<QString, QHash<void*, uint32_t>> s_leaks;
#endif
LeakDetector::LeakDetector() {
#ifndef MVPN_DEBUG
qFatal("LeakDetector _must_ be created in debug builds only!");
#endif
}
LeakDetector::~LeakDetector() {
#ifdef MVPN_DEBUG
QTextStream out(stderr);
out << "== Mozilla VPN - Leak report ===================" << Qt::endl;
bool hasLeaks = false;
for (auto i = s_leaks.begin(); i != s_leaks.end(); ++i) {
QString className = i.key();
if (i->size() == 0) {
continue;
}
hasLeaks = true;
out << className << Qt::endl;
for (auto l = i->begin(); l != i->end(); ++l) {
out << " - ptr: " << l.key() << " size:" << l.value() << Qt::endl;
}
}
if (!hasLeaks) {
out << "No leaks detected." << Qt::endl;
}
#endif
}
#ifdef MVPN_DEBUG
void LeakDetector::logCtor(void* ptr, const char* typeName, uint32_t size) {
QMutexLocker lock(&s_leakDetector);
QString type(typeName);
if (!s_leaks.contains(type)) {
s_leaks.insert(type, QHash<void*, uint32_t>());
}
s_leaks[type].insert(ptr, size);
}
void LeakDetector::logDtor(void* ptr, const char* typeName, uint32_t size) {
QMutexLocker lock(&s_leakDetector);
QString type(typeName);
Q_ASSERT(s_leaks.contains(type));
QHash<void*, uint32_t>& leak = s_leaks[type];
Q_ASSERT(leak.contains(ptr));
Q_ASSERT(leak[ptr] == size);
leak.remove(ptr);
}
#endif

View file

@ -1,41 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LEAKDETECTOR_H
#define LEAKDETECTOR_H
#include <QObject>
#ifdef MVPN_DEBUG
# define MVPN_COUNT_CTOR(_type) \
do { \
static_assert(std::is_class<_type>(), \
"Token '" #_type "' is not a class type."); \
LeakDetector::logCtor((void*)this, #_type, sizeof(*this)); \
} while (0)
# define MVPN_COUNT_DTOR(_type) \
do { \
static_assert(std::is_class<_type>(), \
"Token '" #_type "' is not a class type."); \
LeakDetector::logDtor((void*)this, #_type, sizeof(*this)); \
} while (0)
#else
# define MVPN_COUNT_CTOR(_type)
# define MVPN_COUNT_DTOR(_type)
#endif
class LeakDetector {
public:
LeakDetector();
~LeakDetector();
#ifdef MVPN_DEBUG
static void logCtor(void* ptr, const char* typeName, uint32_t size);
static void logDtor(void* ptr, const char* typeName, uint32_t size);
#endif
};
#endif // LEAKDETECTOR_H

View file

@ -3,23 +3,24 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "dnsutilsmacos.h"
#include "leakdetector.h"
#include "logger.h"
#include <systemconfiguration/scdynamicstore.h>
#include <systemconfiguration/scpreferences.h>
#include <QScopeGuard>
#include <systemconfiguration/scpreferences.h>
#include <systemconfiguration/scdynamicstore.h>
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger(LOG_MACOS, "DnsUtilsMacos");
Logger logger("DnsUtilsMacos");
}
DnsUtilsMacos::DnsUtilsMacos(QObject* parent) : DnsUtils(parent) {
MVPN_COUNT_CTOR(DnsUtilsMacos);
MZ_COUNT_CTOR(DnsUtilsMacos);
m_scStore = SCDynamicStoreCreate(kCFAllocatorSystemDefault,
CFSTR("mozillavpn"), nullptr, nullptr);
CFSTR("amneziavpn"), nullptr, nullptr);
if (m_scStore == nullptr) {
logger.error() << "Failed to create system configuration store ref";
}
@ -28,7 +29,7 @@ DnsUtilsMacos::DnsUtilsMacos(QObject* parent) : DnsUtils(parent) {
}
DnsUtilsMacos::~DnsUtilsMacos() {
MVPN_COUNT_DTOR(DnsUtilsMacos);
MZ_COUNT_DTOR(DnsUtilsMacos);
restoreResolvers();
logger.debug() << "DnsUtilsMacos destroyed.";
}

View file

@ -5,14 +5,14 @@
#ifndef DNSUTILSMACOS_H
#define DNSUTILSMACOS_H
#include "dnsutils.h"
#include <systemconfiguration/scdynamicstore.h>
#include <systemconfiguration/systemconfiguration.h>
#include <QHostAddress>
#include <QMap>
#include <QString>
#include <systemconfiguration/scdynamicstore.h>
#include <systemconfiguration/systemconfiguration.h>
#include "daemon/dnsutils.h"
class DnsUtilsMacos final : public DnsUtils {
Q_OBJECT

View file

@ -3,13 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "iputilsmacos.h"
#include "leakdetector.h"
#include "logger.h"
#include "macosdaemon.h"
#include "daemon/wireguardutils.h"
#include <QHostAddress>
#include <QScopeGuard>
#include <arpa/inet.h>
#include <net/if.h>
@ -19,33 +12,33 @@
#include <sys/ioctl.h>
#include <unistd.h>
#include <QHostAddress>
#include <QScopeGuard>
#include "daemon/wireguardutils.h"
#include "leakdetector.h"
#include "logger.h"
#include "macosdaemon.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace {
Logger logger(LOG_MACOS, "IPUtilsMacos");
Logger logger("IPUtilsMacos");
}
IPUtilsMacos::IPUtilsMacos(QObject* parent) : IPUtils(parent) {
MVPN_COUNT_CTOR(IPUtilsMacos);
MZ_COUNT_CTOR(IPUtilsMacos);
logger.debug() << "IPUtilsMacos created.";
}
IPUtilsMacos::~IPUtilsMacos() {
MVPN_COUNT_DTOR(IPUtilsMacos);
MZ_COUNT_DTOR(IPUtilsMacos);
logger.debug() << "IPUtilsMacos destroyed.";
}
bool IPUtilsMacos::addInterfaceIPs(const InterfaceConfig& config) {
if (!addIP4AddressToDevice(config)) {
return false;
}
if (config.m_ipv6Enabled) {
if (!addIP6AddressToDevice(config)) {
return false;
}
}
return true;
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
}
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
@ -132,7 +125,7 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
// Set ifr to interface
int ret = ioctl(sockfd, SIOCAIFADDR, &ifr);
if (ret) {
logger.error() << "Failed to set IPv4: " << deviceAddr
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
<< "error:" << strerror(errno);
return false;
}
@ -172,7 +165,7 @@ bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
// Set ifr to interface
int ret = ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6);
if (ret) {
logger.error() << "Failed to set IPv6: " << deviceAddr
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
<< "error:" << strerror(errno);
return false;
}

View file

@ -5,10 +5,10 @@
#ifndef IPUTILSMACOS_H
#define IPUTILSMACOS_H
#include "daemon/iputils.h"
#include <arpa/inet.h>
#include "daemon/iputils.h"
class IPUtilsMacos final : public IPUtils {
public:
IPUtilsMacos(QObject* parent);

View file

@ -3,9 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macosdaemon.h"
#include "leakdetector.h"
#include "logger.h"
#include "wgquickprocess.h"
#include <QCoreApplication>
#include <QDir>
@ -18,13 +15,16 @@
#include <QTextStream>
#include <QtGlobal>
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger(LOG_MACOS, "MacOSDaemon");
Logger logger("MacOSDaemon");
MacOSDaemon* s_daemon = nullptr;
} // namespace
MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
MVPN_COUNT_CTOR(MacOSDaemon);
MZ_COUNT_CTOR(MacOSDaemon);
logger.debug() << "Daemon created";
@ -37,7 +37,7 @@ MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
}
MacOSDaemon::~MacOSDaemon() {
MVPN_COUNT_DTOR(MacOSDaemon);
MZ_COUNT_DTOR(MacOSDaemon);
logger.debug() << "Daemon released";
@ -50,25 +50,3 @@ MacOSDaemon* MacOSDaemon::instance() {
Q_ASSERT(s_daemon);
return s_daemon;
}
QByteArray MacOSDaemon::getStatus() {
logger.debug() << "Status request";
bool connected = m_connections.contains(0);
QJsonObject obj;
obj.insert("type", "status");
obj.insert("connected", connected);
if (connected) {
const ConnectionState& state = m_connections.value(0).m_config;
WireguardUtils::peerStatus status =
m_wgutils->getPeerStatus(state.m_config.m_serverPublicKey);
obj.insert("serverIpv4Gateway", state.m_config.m_serverIpv4Gateway);
obj.insert("deviceIpv4Address", state.m_config.m_deviceIpv4Address);
obj.insert("date", state.m_date.toString());
obj.insert("txBytes", QJsonValue(status.txBytes));
obj.insert("rxBytes", QJsonValue(status.rxBytes));
}
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
}

View file

@ -5,7 +5,7 @@
#ifndef MACOSDAEMON_H
#define MACOSDAEMON_H
#include "daemon.h"
#include "daemon/daemon.h"
#include "dnsutilsmacos.h"
#include "iputilsmacos.h"
#include "wireguardutilsmacos.h"
@ -19,8 +19,6 @@ class MacOSDaemon final : public Daemon {
static MacOSDaemon* instance();
QByteArray getStatus() override;
protected:
WireguardUtils* wgutils() const override { return m_wgutils; }
bool supportDnsUtils() const override { return true; }

View file

@ -1,60 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macosdaemonserver.h"
#include "commandlineparser.h"
#include "daemon/daemonlocalserver.h"
#include "leakdetector.h"
#include "logger.h"
#include "macosdaemon.h"
#include "mozillavpn.h"
#include "signalhandler.h"
#include <QCoreApplication>
namespace {
Logger logger(LOG_MACOS, "MacOSDaemonServer");
}
MacOSDaemonServer::MacOSDaemonServer(QObject* parent)
: Command(parent, "macosdaemon", "Activate the macos daemon") {
MVPN_COUNT_CTOR(MacOSDaemonServer);
}
MacOSDaemonServer::~MacOSDaemonServer() { MVPN_COUNT_DTOR(MacOSDaemonServer); }
int MacOSDaemonServer::run(QStringList& tokens) {
Q_ASSERT(!tokens.isEmpty());
QString appName = tokens[0];
QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv());
QCoreApplication::setApplicationName("Mozilla VPN Daemon");
QCoreApplication::setApplicationVersion(APP_VERSION);
if (tokens.length() > 1) {
QList<CommandLineParser::Option*> options;
return CommandLineParser::unknownOption(this, appName, tokens[1], options,
false);
}
MacOSDaemon daemon;
DaemonLocalServer server(qApp);
if (!server.initialize()) {
logger.error() << "Failed to initialize the server";
return 1;
}
// Signal handling for a proper shutdown.
SignalHandler sh;
QObject::connect(&sh, &SignalHandler::quitRequested,
[]() { MacOSDaemon::instance()->deactivate(); });
QObject::connect(&sh, &SignalHandler::quitRequested, &app,
&QCoreApplication::quit, Qt::QueuedConnection);
return app.exec();
}
static Command::RegistrationProxy<MacOSDaemonServer> s_commandMacOSDaemon;

View file

@ -1,18 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MACOSDAEMONSERVER_H
#define MACOSDAEMONSERVER_H
#include "command.h"
class MacOSDaemonServer final : public Command {
public:
explicit MacOSDaemonServer(QObject* parent);
~MacOSDaemonServer();
int run(QStringList& tokens) override;
};
#endif // MACOSDAEMONSERVER_H

View file

@ -3,15 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macosroutemonitor.h"
#include "leakdetector.h"
#include "logger.h"
#include <QCoreApplication>
#include <QHostAddress>
#include <QScopeGuard>
#include <QTimer>
#include <QProcess>
#include <arpa/inet.h>
#include <errno.h>
@ -24,24 +15,21 @@
#include <sys/socket.h>
#include <unistd.h>
#include <QCoreApplication>
#include <QProcess>
#include <QScopeGuard>
#include <QTimer>
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger(LOG_MACOS, "MacosRouteMonitor");
template <typename T>
static T* sockaddr_cast(QByteArray& data) {
const struct sockaddr* sa = (const struct sockaddr*)data.constData();
Q_ASSERT(sa->sa_len <= data.length());
if (data.length() >= (int)sizeof(T)) {
return (T*)data.data();
}
return nullptr;
}
Logger logger("MacosRouteMonitor");
} // namespace
MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent)
: QObject(parent), m_ifname(ifname) {
MVPN_COUNT_CTOR(MacosRouteMonitor);
MZ_COUNT_CTOR(MacosRouteMonitor);
logger.debug() << "MacosRouteMonitor created.";
m_rtsock = socket(PF_ROUTE, SOCK_RAW, 0);
@ -50,96 +38,250 @@ MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent)
return;
}
// Disable replies to our own messages.
int off = 0;
setsockopt(m_rtsock, SOL_SOCKET, SO_USELOOPBACK, &off, sizeof(off));
m_ifindex = if_nametoindex(qPrintable(ifname));
m_notifier = new QSocketNotifier(m_rtsock, QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this,
&MacosRouteMonitor::rtsockReady);
// Grab the default routes at startup.
rtmFetchRoutes(AF_INET);
rtmFetchRoutes(AF_INET6);
}
MacosRouteMonitor::~MacosRouteMonitor() {
MVPN_COUNT_DTOR(MacosRouteMonitor);
MZ_COUNT_DTOR(MacosRouteMonitor);
flushExclusionRoutes();
if (m_rtsock >= 0) {
close(m_rtsock);
}
logger.debug() << "MacosRouteMonitor destroyed.";
}
void MacosRouteMonitor::handleRtmAdd(const struct rt_msghdr* rtm,
const QByteArray& payload) {
QList<QByteArray> addrlist = parseAddrList(payload);
// Ignore routing changes on the tunnel interfaces
if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) {
if (m_ifname == addrToString(addrlist[1])) {
return;
}
// Compare memory against zero.
static int memcmpzero(const void* data, size_t len) {
const quint8* ptr = static_cast<const quint8*>(data);
while (len--) {
if (*ptr++) return 1;
}
QStringList list;
for (auto addr : addrlist) {
list.append(addrToString(addr));
}
logger.debug() << "Route added by" << rtm->rtm_pid
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
return 0;
}
void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm,
const QByteArray& payload) {
QList<QByteArray> addrlist = parseAddrList(payload);
// Ignore routing changes on the tunnel interfaces
if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) {
if (m_ifname == addrToString(addrlist[1])) {
return;
}
// Ignore routing changes on the tunnel interface.
if (rtm->rtm_index == m_ifindex) {
return;
}
QStringList list;
#ifdef MZ_DEBUG
for (auto addr : addrlist) {
list.append(addrToString(addr));
}
logger.debug() << "Route deleted by" << rtm->rtm_pid
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
#endif
char ifname[IF_NAMESIZE] = "null";
if (rtm->rtm_index != 0) {
if_indextoname(rtm->rtm_index, ifname);
}
logger.debug() << "Route deleted via" << ifname
<< QString("addrs(%1):").arg(rtm->rtm_addrs, 0, 16)
<< list.join(" ");
// We expect all useful routes to contain a destination, netmask and gateway.
if (!(rtm->rtm_addrs & RTA_DST) || !(rtm->rtm_addrs & RTA_GATEWAY) ||
!(rtm->rtm_addrs & RTA_NETMASK) || (addrlist.count() < 3)) {
return;
}
// Check for a default route, which should have a netmask of zero.
const struct sockaddr* sa =
reinterpret_cast<const struct sockaddr*>(addrlist[2].constData());
if (sa->sa_family == AF_INET) {
struct sockaddr_in sin;
Q_ASSERT(sa->sa_len <= sizeof(sin));
memset(&sin, 0, sizeof(sin));
memcpy(&sin, sa, sa->sa_len);
if (memcmpzero(&sin.sin_addr, sizeof(sin.sin_addr)) != 0) {
return;
}
} else if (sa->sa_family == AF_INET6) {
struct sockaddr_in6 sin6;
Q_ASSERT(sa->sa_len <= sizeof(sin6));
memset(&sin6, 0, sizeof(sin6));
memcpy(&sin6, sa, sa->sa_len);
if (memcmpzero(&sin6.sin6_addr, sizeof(sin6.sin6_addr)) != 0) {
return;
}
} else if (sa->sa_family != AF_UNSPEC) {
// We have sometimes seen the default route reported with AF_UNSPEC.
return;
}
// Clear the default gateway
const struct sockaddr* dst =
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
QAbstractSocket::NetworkLayerProtocol protocol;
unsigned int plen;
if (dst->sa_family == AF_INET) {
m_defaultGatewayIpv4.clear();
m_defaultIfindexIpv4 = 0;
protocol = QAbstractSocket::IPv4Protocol;
plen = 32;
} else if (dst->sa_family == AF_INET6) {
m_defaultGatewayIpv6.clear();
m_defaultIfindexIpv6 = 0;
protocol = QAbstractSocket::IPv6Protocol;
plen = 128;
}
logger.debug() << "Lost default route via" << ifname
<< logger.sensitive(addrToString(addrlist[1]));
for (const QHostAddress& addr : m_exclusionRoutes) {
if (addr.protocol() == protocol) {
logger.debug() << "Removing exclusion route to"
<< logger.sensitive(addr.toString());
rtmSendRoute(RTM_DELETE, addr, plen, rtm->rtm_index, nullptr);
}
}
}
void MacosRouteMonitor::handleRtmChange(const struct rt_msghdr* rtm,
void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
const QByteArray& payload) {
QList<QByteArray> addrlist = parseAddrList(payload);
int ifindex = rtm->rtm_index;
char ifname[IF_NAMESIZE] = "null";
// Ignore routing changes on the tunnel interfaces
if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) {
if (m_ifname == addrToString(addrlist[1])) {
// We expect all useful routes to contain a destination, netmask and gateway.
if (!(rtm->rtm_addrs & RTA_DST) || !(rtm->rtm_addrs & RTA_GATEWAY) ||
!(rtm->rtm_addrs & RTA_NETMASK) || (addrlist.count() < 3)) {
return;
}
// Ignore route changes that we caused, or routes on the tunnel interface.
if (rtm->rtm_index == m_ifindex) {
return;
}
if ((rtm->rtm_pid == getpid()) && (rtm->rtm_type != RTM_GET)) {
return;
}
// Special case: If RTA_IFP is set, then we should get the interface index
// from the address list instead of rtm_index.
if (rtm->rtm_addrs & RTA_IFP) {
int addridx = 0;
for (int mask = 1; mask < RTA_IFP; mask <<= 1) {
if (rtm->rtm_addrs & mask) {
addridx++;
}
}
if (addridx >= addrlist.count()) {
return;
}
const char* sdl_data = addrlist[addridx].constData();
const struct sockaddr_dl* sdl =
reinterpret_cast<const struct sockaddr_dl*>(sdl_data);
if (sdl->sdl_family == AF_LINK) {
ifindex = sdl->sdl_index;
}
}
// Log relevant updates to the routing table.
QStringList list;
#ifdef MZ_DEBUG
for (auto addr : addrlist) {
list.append(addrToString(addr));
}
logger.debug() << "Route changed by" << rtm->rtm_pid
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
#endif
if_indextoname(ifindex, ifname);
logger.debug() << "Route update via" << ifname
<< QString("addrs(%1):").arg(rtm->rtm_addrs, 0, 16)
<< list.join(" ");
// Check for a default route, which should have a netmask of zero.
const struct sockaddr* sa =
reinterpret_cast<const struct sockaddr*>(addrlist[2].constData());
if (sa->sa_family == AF_INET) {
struct sockaddr_in sin;
Q_ASSERT(sa->sa_len <= sizeof(sin));
memset(&sin, 0, sizeof(sin));
memcpy(&sin, sa, sa->sa_len);
if (memcmpzero(&sin.sin_addr, sizeof(sin.sin_addr)) != 0) {
return;
}
} else if (sa->sa_family == AF_INET6) {
struct sockaddr_in6 sin6;
Q_ASSERT(sa->sa_len <= sizeof(sin6));
memset(&sin6, 0, sizeof(sin6));
memcpy(&sin6, sa, sa->sa_len);
if (memcmpzero(&sin6.sin6_addr, sizeof(sin6.sin6_addr)) != 0) {
return;
}
} else if (sa->sa_family != AF_UNSPEC) {
// The default route sometimes sets a netmask of AF_UNSPEC.
return;
}
// Determine if this is the IPv4 or IPv6 default route.
const struct sockaddr* dst =
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
QAbstractSocket::NetworkLayerProtocol protocol;
unsigned int plen;
int rtm_type = RTM_ADD;
if (dst->sa_family == AF_INET) {
if (m_defaultIfindexIpv4 != 0) {
rtm_type = RTM_CHANGE;
}
m_defaultGatewayIpv4 = addrlist[1];
m_defaultIfindexIpv4 = ifindex;
protocol = QAbstractSocket::IPv4Protocol;
plen = 32;
} else if (dst->sa_family == AF_INET6) {
if (m_defaultIfindexIpv6 != 0) {
rtm_type = RTM_CHANGE;
}
m_defaultGatewayIpv6 = addrlist[1];
m_defaultIfindexIpv6 = ifindex;
protocol = QAbstractSocket::IPv6Protocol;
plen = 128;
} else {
return;
}
// Update the exclusion routes with the new default route.
logger.debug() << "Updating default route via" << ifname
<< addrToString(addrlist[1]);
for (const QHostAddress& addr : m_exclusionRoutes) {
if (addr.protocol() == protocol) {
logger.debug() << "Updating exclusion route to"
<< logger.sensitive(addr.toString());
rtmSendRoute(rtm_type, addr, plen, ifindex, addrlist[1].constData());
}
}
}
void MacosRouteMonitor::handleIfaceInfo(const struct if_msghdr* ifm,
const QByteArray& payload) {
QList<QByteArray> addrlist = parseAddrList(payload);
QStringList list;
if (ifm->ifm_index != if_nametoindex(qPrintable(m_ifname))) {
return;
}
m_ifflags = ifm->ifm_flags;
QStringList list;
#ifdef MZ_DEBUG
QList<QByteArray> addrlist = parseAddrList(payload);
for (auto addr : addrlist) {
list.append(addrToString(addr));
}
logger.debug() << "Interface " << ifm->ifm_index
<< "changed flags:" << ifm->ifm_flags
<< QString("addrs(%1):").arg(ifm->ifm_addrs) << list.join(" ");
#else
Q_UNUSED(payload);
#endif
logger.debug() << "Interface" << ifm->ifm_index
<< "chagned flags:" << ifm->ifm_flags
<< QString("addrs(%1):").arg(ifm->ifm_addrs, 0, 16)
<< list.join(" ");
}
void MacosRouteMonitor::rtsockReady() {
@ -154,11 +296,13 @@ void MacosRouteMonitor::rtsockReady() {
(struct rt_msghdr*)((char*)(_rtm_) + (_rtm_)->rtm_msglen)
#endif
struct rt_msghdr* rtm = (struct rt_msghdr*)buf;
struct rt_msghdr* end = (struct rt_msghdr*)(&buf[len]);
struct rt_msghdr* rtm = reinterpret_cast<struct rt_msghdr*>(buf);
struct rt_msghdr* end = reinterpret_cast<struct rt_msghdr*>(&buf[len]);
while (rtm < end) {
// Ensure the message fits within the buffer
if (RTMSG_NEXT(rtm) > end) {
logger.debug() << "Routing message overflowed with length"
<< rtm->rtm_msglen;
break;
}
@ -167,7 +311,7 @@ void MacosRouteMonitor::rtsockReady() {
switch (rtm->rtm_type) {
case RTM_ADD:
message.remove(0, sizeof(struct rt_msghdr));
handleRtmAdd(rtm, message);
handleRtmUpdate(rtm, message);
break;
case RTM_DELETE:
message.remove(0, sizeof(struct rt_msghdr));
@ -175,14 +319,17 @@ void MacosRouteMonitor::rtsockReady() {
break;
case RTM_CHANGE:
message.remove(0, sizeof(struct rt_msghdr));
handleRtmChange(rtm, message);
handleRtmUpdate(rtm, message);
break;
case RTM_GET:
message.remove(0, sizeof(struct rt_msghdr));
handleRtmUpdate(rtm, message);
break;
case RTM_IFINFO:
message.remove(0, sizeof(struct if_msghdr));
handleIfaceInfo((struct if_msghdr*)rtm, message);
break;
default:
logger.debug() << "Unknown routing message:" << rtm->rtm_type;
break;
}
@ -192,7 +339,7 @@ void MacosRouteMonitor::rtsockReady() {
void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
int rtaddr, const void* sa) {
size_t sa_len = ((struct sockaddr*)sa)->sa_len;
size_t sa_len = static_cast<const struct sockaddr*>(sa)->sa_len;
Q_ASSERT((rtm->rtm_addrs & rtaddr) == 0);
if ((rtm->rtm_msglen + sa_len) > maxlen) {
return;
@ -206,18 +353,19 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
}
}
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix,
unsigned int plen, unsigned int ifindex,
const void* gateway) {
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
sizeof(struct sockaddr_in6) * 2 +
sizeof(struct sockaddr_dl);
char buf[rtm_max_size];
struct rt_msghdr* rtm = (struct rt_msghdr*)buf;
sizeof(struct sockaddr_storage);
char buf[rtm_max_size] = {0};
struct rt_msghdr* rtm = reinterpret_cast<struct rt_msghdr*>(buf);
memset(rtm, 0, rtm_max_size);
rtm->rtm_msglen = sizeof(struct rt_msghdr);
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = action;
rtm->rtm_index = if_nametoindex(qPrintable(m_ifname));
rtm->rtm_index = ifindex;
rtm->rtm_flags = RTF_STATIC | RTF_UP;
rtm->rtm_addrs = 0;
rtm->rtm_pid = 0;
@ -227,9 +375,9 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
memset(&rtm->rtm_rmx, 0, sizeof(rtm->rtm_rmx));
// Append RTA_DST
if (prefix.type() == IPAddressRange::IPv6) {
if (prefix.protocol() == QAbstractSocket::IPv6Protocol) {
struct sockaddr_in6 sin6;
Q_IPV6ADDR dst = QHostAddress(prefix.ipAddress()).toIPv6Address();
Q_IPV6ADDR dst = prefix.toIPv6Address();
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
sin6.sin6_len = sizeof(sin6);
@ -237,7 +385,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6);
} else {
struct sockaddr_in sin;
quint32 dst = QHostAddress(prefix.ipAddress()).toIPv4Address();
quint32 dst = prefix.toIPv4Address();
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_len = sizeof(sin);
@ -246,23 +394,16 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
}
// Append RTA_GATEWAY
if (action != RTM_DELETE) {
struct sockaddr_dl sdl;
memset(&sdl, 0, sizeof(sdl));
sdl.sdl_family = AF_LINK;
sdl.sdl_len = offsetof(struct sockaddr_dl, sdl_data) + m_ifname.length();
sdl.sdl_index = rtm->rtm_index;
sdl.sdl_type = IFT_OTHER;
sdl.sdl_nlen = m_ifname.length();
sdl.sdl_alen = 0;
sdl.sdl_slen = 0;
memcpy(&sdl.sdl_data, qPrintable(m_ifname), sdl.sdl_nlen);
rtmAppendAddr(rtm, rtm_max_size, RTA_GATEWAY, &sdl);
if (gateway != nullptr) {
int family = static_cast<const struct sockaddr*>(gateway)->sa_family;
if ((family == AF_INET) || (family == AF_INET6)) {
rtm->rtm_flags |= RTF_GATEWAY;
}
rtmAppendAddr(rtm, rtm_max_size, RTA_GATEWAY, gateway);
}
// Append RTA_NETMASK
int plen = prefix.range();
if (prefix.type() == IPAddressRange::IPv6) {
if (prefix.protocol() == QAbstractSocket::IPv6Protocol) {
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6;
@ -272,7 +413,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
sin6.sin6_addr.s6_addr[plen / 8] = 0xFF ^ (0xFF >> (plen % 8));
}
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin6);
} else if (prefix.type() == IPAddressRange::IPv4) {
} else if (prefix.protocol() == QAbstractSocket::IPv4Protocol) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
@ -299,12 +440,122 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
return false;
}
bool MacosRouteMonitor::insertRoute(const IPAddressRange& prefix) {
return rtmSendRoute(RTM_ADD, prefix);
bool MacosRouteMonitor::rtmFetchRoutes(int family) {
constexpr size_t rtm_max_size =
sizeof(struct rt_msghdr) + sizeof(struct sockaddr_storage) * 2;
char buf[rtm_max_size] = {0};
struct rt_msghdr* rtm = reinterpret_cast<struct rt_msghdr*>(buf);
rtm->rtm_msglen = sizeof(struct rt_msghdr);
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = RTM_GET;
rtm->rtm_flags = RTF_UP | RTF_GATEWAY;
rtm->rtm_addrs = 0;
rtm->rtm_pid = 0;
rtm->rtm_seq = m_rtseq++;
rtm->rtm_errno = 0;
rtm->rtm_inits = 0;
memset(&rtm->rtm_rmx, 0, sizeof(rtm->rtm_rmx));
if (family == AF_INET) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_len = sizeof(struct sockaddr_in);
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin);
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin);
} else if (family == AF_INET6) {
struct sockaddr_in6 sin6;
memset(&sin6, 0, sizeof(struct sockaddr_in6));
sin6.sin6_family = AF_INET6;
sin6.sin6_len = sizeof(struct sockaddr_in6);
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6);
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin6);
} else {
logger.warning() << "Unsupported address family";
return false;
}
// Send the routing message into the kernel.
int len = write(m_rtsock, rtm, rtm->rtm_msglen);
if (len == rtm->rtm_msglen) {
return true;
}
logger.warning() << "Failed to request routing table:" << strerror(errno);
return false;
}
bool MacosRouteMonitor::deleteRoute(const IPAddressRange& prefix) {
return rtmSendRoute(RTM_DELETE, prefix);
bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
struct sockaddr_dl datalink;
memset(&datalink, 0, sizeof(datalink));
datalink.sdl_family = AF_LINK;
datalink.sdl_len = offsetof(struct sockaddr_dl, sdl_data) + m_ifname.length();
datalink.sdl_index = m_ifindex;
datalink.sdl_type = IFT_OTHER;
datalink.sdl_nlen = m_ifname.length();
datalink.sdl_alen = 0;
datalink.sdl_slen = 0;
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
return rtmSendRoute(RTM_ADD, prefix.address(), prefix.prefixLength(),
m_ifindex, &datalink);
}
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
return rtmSendRoute(RTM_DELETE, prefix.address(), prefix.prefixLength(),
m_ifindex, nullptr);
}
bool MacosRouteMonitor::addExclusionRoute(const QHostAddress& address) {
logger.debug() << "Adding exclusion route for"
<< logger.sensitive(address.toString());
if (m_exclusionRoutes.contains(address)) {
logger.warning() << "Exclusion route already exists";
return false;
}
m_exclusionRoutes.append(address);
// If the default route is known, then updte the routing table immediately.
if ((address.protocol() == QAbstractSocket::IPv4Protocol) &&
(m_defaultIfindexIpv4 != 0) && !m_defaultGatewayIpv4.isEmpty()) {
return rtmSendRoute(RTM_ADD, address, 32, m_defaultIfindexIpv4,
m_defaultGatewayIpv4.constData());
}
if ((address.protocol() == QAbstractSocket::IPv6Protocol) &&
(m_defaultIfindexIpv6 != 0) && !m_defaultGatewayIpv6.isEmpty()) {
return rtmSendRoute(RTM_ADD, address, 128, m_defaultIfindexIpv6,
m_defaultGatewayIpv6.constData());
}
// Otherwise, the default route isn't known yet. Do nothing.
return true;
}
bool MacosRouteMonitor::deleteExclusionRoute(const QHostAddress& address) {
logger.debug() << "Deleting exclusion route for"
<< logger.sensitive(address.toString());
m_exclusionRoutes.removeAll(address);
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
return rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr);
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
return rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6,
nullptr);
} else {
return false;
}
}
void MacosRouteMonitor::flushExclusionRoutes() {
while (!m_exclusionRoutes.isEmpty()) {
QHostAddress address = m_exclusionRoutes.takeFirst();
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr);
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6, nullptr);
}
}
}
// static
@ -341,7 +592,10 @@ QString MacosRouteMonitor::addrToString(const struct sockaddr* sa) {
}
if (sa->sa_family == AF_LINK) {
const struct sockaddr_dl* sdl = (const struct sockaddr_dl*)sa;
return QString(link_ntoa(sdl));
return QString("link#%1:").arg(sdl->sdl_index) + QString(link_ntoa(sdl));
}
if (sa->sa_family == AF_UNSPEC) {
return QString("unspec");
}
return QString("unknown(af=%1)").arg(sa->sa_family);
}

View file

@ -5,13 +5,14 @@
#ifndef MACOSROUTEMONITOR_H
#define MACOSROUTEMONITOR_H
#include "ipaddressrange.h"
#include <QByteArray>
#include <QHostAddress>
#include <QList>
#include <QObject>
#include <QSocketNotifier>
#include "ipaddress.h"
struct if_msghdr;
struct rt_msghdr;
struct sockaddr;
@ -23,16 +24,21 @@ class MacosRouteMonitor final : public QObject {
MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr);
~MacosRouteMonitor();
bool insertRoute(const IPAddressRange& prefix);
bool deleteRoute(const IPAddressRange& prefix);
bool insertRoute(const IPAddress& prefix);
bool deleteRoute(const IPAddress& prefix);
int interfaceFlags() { return m_ifflags; }
bool addExclusionRoute(const QHostAddress& address);
bool deleteExclusionRoute(const QHostAddress& address);
void flushExclusionRoutes();
private:
void handleRtmAdd(const struct rt_msghdr* msg, const QByteArray& payload);
void handleRtmDelete(const struct rt_msghdr* msg, const QByteArray& payload);
void handleRtmChange(const struct rt_msghdr* msg, const QByteArray& payload);
void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload);
void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
bool rtmSendRoute(int action, const IPAddressRange& prefix);
bool rtmSendRoute(int action, const QHostAddress& prefix, unsigned int plen,
unsigned int ifindex, const void* gateway);
bool rtmFetchRoutes(int family);
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
const void* sa);
static QList<QByteArray> parseAddrList(const QByteArray& data);
@ -44,7 +50,14 @@ class MacosRouteMonitor final : public QObject {
static QString addrToString(const struct sockaddr* sa);
static QString addrToString(const QByteArray& data);
QList<QHostAddress> m_exclusionRoutes;
QByteArray m_defaultGatewayIpv4;
QByteArray m_defaultGatewayIpv6;
unsigned int m_defaultIfindexIpv4 = 0;
unsigned int m_defaultIfindexIpv6 = 0;
QString m_ifname;
unsigned int m_ifindex = 0;
int m_ifflags = 0;
int m_rtsock = -1;
int m_rtseq = 0;

View file

@ -3,8 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "wireguardutilsmacos.h"
#include "leakdetector.h"
#include "logger.h"
#include <errno.h>
#include <QByteArray>
#include <QDir>
@ -12,19 +12,20 @@
#include <QLocalSocket>
#include <QTimer>
#include <errno.h>
#include "leakdetector.h"
#include "logger.h"
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard";
namespace {
Logger logger(LOG_MACOS, "WireguardUtilsMacos");
Logger logwireguard(LOG_MACOS, "WireguardGo");
Logger logger("WireguardUtilsMacos");
Logger logwireguard("WireguardGo");
}; // namespace
WireguardUtilsMacos::WireguardUtilsMacos(QObject* parent)
: WireguardUtils(parent), m_tunnel(this) {
MVPN_COUNT_CTOR(WireguardUtilsMacos);
MZ_COUNT_CTOR(WireguardUtilsMacos);
logger.debug() << "WireguardUtilsMacos created.";
connect(&m_tunnel, SIGNAL(readyReadStandardOutput()), this,
@ -34,7 +35,7 @@ WireguardUtilsMacos::WireguardUtilsMacos(QObject* parent)
}
WireguardUtilsMacos::~WireguardUtilsMacos() {
MVPN_COUNT_DTOR(WireguardUtilsMacos);
MZ_COUNT_DTOR(WireguardUtilsMacos);
logger.debug() << "WireguardUtilsMacos destroyed.";
}
@ -68,15 +69,12 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name");
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
#ifdef QT_DEBUG
#ifdef MZ_DEBUG
pe.insert("LOG_LEVEL", "debug");
#endif
m_tunnel.setProcessEnvironment(pe);
QDir appPath(QCoreApplication::applicationDirPath());
appPath.cdUp();
appPath.cd("Resources");
appPath.cd("utils");
QStringList wgArgs = {"-f", "utun"};
m_tunnel.start(appPath.filePath("wireguard-go"), wgArgs);
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
@ -99,6 +97,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
// Send a UAPI command to configure the interface
QString message("set=1\n");
QByteArray privateKey = QByteArray::fromBase64(config.m_privateKey.toUtf8());
QTextStream out(&message);
out << "private_key=" << QString(privateKey.toHex()) << "\n";
out << "replace_peers=true\n";
@ -134,14 +133,15 @@ bool WireguardUtilsMacos::deleteInterface() {
// dummy implementations for now
bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
QByteArray pskKey = QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
// Update/create the peer config
QString message;
QTextStream out(&message);
out << "set=1\n";
out << "public_key=" << QString(publicKey.toHex()) << "\n";
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
if (!config.m_serverIpv4AddrIn.isNull()) {
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
} else if (!config.m_serverIpv6AddrIn.isNull()) {
@ -153,10 +153,12 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
out << config.m_serverPort << "\n";
out << "replace_allowed_ips=true\n";
for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) {
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
out << "allowed_ip=" << ip.toString() << "\n";
}
logger.debug() << message;
int err = uapiErrno(uapiCommand(message));
if (err != 0) {
logger.error() << "Peer configuration failed:" << strerror(err);
@ -164,8 +166,9 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
return (err == 0);
}
bool WireguardUtilsMacos::deletePeer(const QString& pubkey) {
QByteArray publicKey = QByteArray::fromBase64(qPrintable(pubkey));
bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) {
QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
QString message;
QTextStream out(&message);
@ -180,13 +183,10 @@ bool WireguardUtilsMacos::deletePeer(const QString& pubkey) {
return (err == 0);
}
WireguardUtils::peerStatus WireguardUtilsMacos::getPeerStatus(
const QString& pubkey) {
peerStatus status = {0, 0};
QString hexkey = QByteArray::fromBase64(pubkey.toUtf8()).toHex();
QList<WireguardUtils::PeerStatus> WireguardUtilsMacos::getPeerStatus() {
QString reply = uapiCommand("get=1");
bool match = false;
PeerStatus status;
QList<PeerStatus> peerList;
for (const QString& line : reply.split('\n')) {
int eq = line.indexOf('=');
if (eq <= 0) {
@ -196,39 +196,90 @@ WireguardUtils::peerStatus WireguardUtilsMacos::getPeerStatus(
QString value = line.mid(eq + 1);
if (name == "public_key") {
match = (value == hexkey);
continue;
} else if (!match) {
continue;
if (!status.m_pubkey.isEmpty()) {
peerList.append(status);
}
QByteArray pubkey = QByteArray::fromHex(value.toUtf8());
status = PeerStatus(pubkey.toBase64());
}
if (name == "tx_bytes") {
status.txBytes = value.toDouble();
status.m_txBytes = value.toDouble();
}
if (name == "rx_bytes") {
status.rxBytes = value.toDouble();
status.m_rxBytes = value.toDouble();
}
if (name == "last_handshake_time_sec") {
status.m_handshake += value.toLongLong() * 1000;
}
if (name == "last_handshake_time_nsec") {
status.m_handshake += value.toLongLong() / 1000000;
}
}
if (!status.m_pubkey.isEmpty()) {
peerList.append(status);
}
return status;
return peerList;
}
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddressRange& prefix,
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix,
int hopindex) {
Q_UNUSED(hopindex);
if (!m_rtmonitor) {
return false;
}
return m_rtmonitor->insertRoute(prefix);
if (prefix.prefixLength() > 0) {
return m_rtmonitor->insertRoute(prefix);
}
// Ensure that we do not replace the default route.
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
return m_rtmonitor->insertRoute(IPAddress("0.0.0.0/1")) &&
m_rtmonitor->insertRoute(IPAddress("128.0.0.0/1"));
}
if (prefix.type() == QAbstractSocket::IPv6Protocol) {
return m_rtmonitor->insertRoute(IPAddress("::/1")) &&
m_rtmonitor->insertRoute(IPAddress("8000::/1"));
}
return false;
}
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddressRange& prefix,
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix,
int hopindex) {
Q_UNUSED(hopindex);
if (!m_rtmonitor) {
return false;
}
return m_rtmonitor->deleteRoute(prefix);
if (prefix.prefixLength() > 0) {
return m_rtmonitor->insertRoute(prefix);
}
// Ensure that we do not replace the default route.
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
m_rtmonitor->deleteRoute(IPAddress("128.0.0.0/1"));
} else if (prefix.type() == QAbstractSocket::IPv6Protocol) {
return m_rtmonitor->deleteRoute(IPAddress("::/1")) &&
m_rtmonitor->deleteRoute(IPAddress("8000::/1"));
} else {
return false;
}
}
bool WireguardUtilsMacos::addExclusionRoute(const QHostAddress& address) {
if (!m_rtmonitor) {
return false;
}
return m_rtmonitor->addExclusionRoute(address);
}
bool WireguardUtilsMacos::deleteExclusionRoute(const QHostAddress& address) {
if (!m_rtmonitor) {
return false;
}
return m_rtmonitor->deleteExclusionRoute(address);
}
QString WireguardUtilsMacos::uapiCommand(const QString& command) {

View file

@ -5,12 +5,12 @@
#ifndef WIREGUARDUTILSMACOS_H
#define WIREGUARDUTILSMACOS_H
#include "daemon/wireguardutils.h"
#include "macosroutemonitor.h"
#include <QObject>
#include <QProcess>
#include "daemon/wireguardutils.h"
#include "macosroutemonitor.h"
class WireguardUtilsMacos final : public WireguardUtils {
Q_OBJECT
@ -26,11 +26,14 @@ class WireguardUtilsMacos final : public WireguardUtils {
bool deleteInterface() override;
bool updatePeer(const InterfaceConfig& config) override;
bool deletePeer(const QString& pubkey) override;
peerStatus getPeerStatus(const QString& pubkey) override;
bool deletePeer(const InterfaceConfig& config) override;
QList<PeerStatus> getPeerStatus() override;
bool updateRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
bool deleteRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
bool updateRoutePrefix(const IPAddress& prefix, int hopindex) override;
bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) override;
bool addExclusionRoute(const QHostAddress& address) override;
bool deleteExclusionRoute(const QHostAddress& address) override;
signals:
void backendFailure();

View file

@ -1,136 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "cryptosettings.h"
#include "logger.h"
#include <QRandomGenerator>
constexpr const NSString* SERVICE = @"Mozilla VPN";
#import <Foundation/Foundation.h>
namespace {
Logger logger({LOG_MACOS, LOG_MAIN}, "MacOSCryptoSettings");
bool initialized = false;
QByteArray key;
} // anonymous
// static
void CryptoSettings::resetKey() {
logger.debug() << "Reset the key in the keychain";
NSData* service = [SERVICE dataUsingEncoding:NSUTF8StringEncoding];
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
NSMutableDictionary* query = [[NSMutableDictionary alloc] init];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:service forKey:(id)kSecAttrGeneric];
[query setObject:service forKey:(id)kSecAttrAccount];
[query setObject:appId forKey:(id)kSecAttrService];
SecItemDelete((CFDictionaryRef)query);
[query release];
initialized = false;
}
// static
bool CryptoSettings::getKey(uint8_t output[CRYPTO_SETTINGS_KEY_SIZE]) {
#if defined(MVPN_IOS) || defined(MVPN_MACOS_NETWORKEXTENSION) || defined(MVPN_MACOS_DAEMON)
if (!initialized) {
initialized = true;
logger.debug() << "Retrieving the key from the keychain";
NSData* service = [SERVICE dataUsingEncoding:NSUTF8StringEncoding];
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
NSMutableDictionary* query = [[NSMutableDictionary alloc] init];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:service forKey:(id)kSecAttrGeneric];
[query setObject:service forKey:(id)kSecAttrAccount];
[query setObject:appId forKey:(id)kSecAttrService];
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
NSData* keyData = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)(void*)&keyData);
[query release];
if (status == noErr) {
key = QByteArray::fromNSData(keyData);
logger.debug() << "Key found with length:" << key.length();
if (key.length() == CRYPTO_SETTINGS_KEY_SIZE) {
memcpy(output, key.data(), CRYPTO_SETTINGS_KEY_SIZE);
return true;
}
}
logger.warning() << "Key not found. Let's create it. Error:" << status;
key = QByteArray(CRYPTO_SETTINGS_KEY_SIZE, 0x00);
QRandomGenerator* rg = QRandomGenerator::system();
for (int i = 0; i < CRYPTO_SETTINGS_KEY_SIZE; ++i) {
key[i] = rg->generate() & 0xFF;
}
query = [[NSMutableDictionary alloc] init];
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
[query setObject:service forKey:(id)kSecAttrGeneric];
[query setObject:service forKey:(id)kSecAttrAccount];
[query setObject:appId forKey:(id)kSecAttrService];
SecItemDelete((CFDictionaryRef)query);
keyData = key.toNSData();
[query setObject:keyData forKey:(id)kSecValueData];
status = SecItemAdd((CFDictionaryRef)query, NULL);
if (status != noErr) {
logger.error() << "Failed to store the key. Error:" << status;
key = QByteArray();
}
[query release];
}
if (key.length() == CRYPTO_SETTINGS_KEY_SIZE) {
memcpy(output, key.data(), CRYPTO_SETTINGS_KEY_SIZE);
return true;
}
logger.error() << "Invalid key";
#else
Q_UNUSED(output);
#endif
return false;
}
// static
CryptoSettings::Version CryptoSettings::getSupportedVersion() {
logger.debug() << "Get supported settings method";
#if defined(MVPN_IOS) || defined(MVPN_MACOS_NETWORKEXTENSION) || defined(MVPN_MACOS_DAEMON)
uint8_t key[CRYPTO_SETTINGS_KEY_SIZE];
if (getKey(key)) {
logger.debug() << "Encryption supported!";
return CryptoSettings::EncryptionChachaPolyV1;
}
#endif
logger.debug() << "No encryption";
return CryptoSettings::NoEncryption;
}

View file

@ -1,106 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macosmenubar.h"
#include "leakdetector.h"
#include "logger.h"
#include "mozillavpn.h"
#include "qmlengineholder.h"
#ifdef MVPN_MACOS
# include "platforms/macos/macosutils.h"
#endif
#include <QAction>
#include <QMenu>
#include <QMenuBar>
namespace {
Logger logger(LOG_MACOS, "MacOSManuBar");
MacOSMenuBar* s_instance = nullptr;
} // namespace
MacOSMenuBar::MacOSMenuBar() {
MVPN_COUNT_CTOR(MacOSMenuBar);
Q_ASSERT(!s_instance);
s_instance = this;
}
MacOSMenuBar::~MacOSMenuBar() {
MVPN_COUNT_DTOR(MacOSMenuBar);
Q_ASSERT(s_instance == this);
s_instance = nullptr;
}
// static
MacOSMenuBar* MacOSMenuBar::instance() {
Q_ASSERT(s_instance);
return s_instance;
}
void MacOSMenuBar::initialize() {
logger.debug() << "Creating menubar";
AmneziaVPN* vpn = AmneziaVPN::instance();
m_menuBar = new QMenuBar(nullptr);
//% "File"
QMenu* fileMenu = m_menuBar->addMenu(qtTrId("menubar.file.title"));
// Do not use qtTrId here!
QAction* quit =
fileMenu->addAction("quit", vpn->controller(), &Controller::quit);
quit->setMenuRole(QAction::QuitRole);
// Do not use qtTrId here!
m_aboutAction =
fileMenu->addAction("about.vpn", vpn, &AmneziaVPN::requestAbout);
m_aboutAction->setMenuRole(QAction::AboutRole);
m_aboutAction->setVisible(vpn->state() == AmneziaVPN::StateMain);
// Do not use qtTrId here!
m_preferencesAction =
fileMenu->addAction("preferences", vpn, &AmneziaVPN::requestSettings);
m_preferencesAction->setMenuRole(QAction::PreferencesRole);
m_preferencesAction->setVisible(vpn->state() == AmneziaVPN::StateMain);
m_closeAction = fileMenu->addAction("close", []() {
QmlEngineHolder::instance()->hideWindow();
#ifdef MVPN_MACOS
MacOSUtils::hideDockIcon();
#endif
});
m_closeAction->setShortcut(QKeySequence::Close);
m_helpMenu = m_menuBar->addMenu("");
retranslate();
};
void MacOSMenuBar::controllerStateChanged() {
AmneziaVPN* vpn = AmneziaVPN::instance();
m_preferencesAction->setVisible(vpn->state() == AmneziaVPN::StateMain);
m_aboutAction->setVisible(vpn->state() == AmneziaVPN::StateMain);
}
void MacOSMenuBar::retranslate() {
logger.debug() << "Retranslate";
//% "Close"
m_closeAction->setText(qtTrId("menubar.file.close"));
//% "Help"
m_helpMenu->setTitle(qtTrId("menubar.help.title"));
for (QAction* action : m_helpMenu->actions()) {
m_helpMenu->removeAction(action);
}
AmneziaVPN* vpn = AmneziaVPN::instance();
vpn->helpModel()->forEach([&](const char* nameId, int id) {
m_helpMenu->addAction(qtTrId(nameId),
[help = vpn->helpModel(), id]() { help->open(id); });
});
}

View file

@ -1,42 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MACOSMENUBAR_H
#define MACOSMENUBAR_H
#include <QObject>
class QAction;
class QMenu;
class QMenuBar;
class MacOSMenuBar final : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(MacOSMenuBar)
public:
MacOSMenuBar();
~MacOSMenuBar();
static MacOSMenuBar* instance();
void initialize();
void retranslate();
QMenuBar* menuBar() const { return m_menuBar; }
public slots:
void controllerStateChanged();
private:
QMenuBar* m_menuBar = nullptr;
QAction* m_aboutAction = nullptr;
QAction* m_preferencesAction = nullptr;
QAction* m_closeAction = nullptr;
QMenu* m_helpMenu = nullptr;
};
#endif // MACOSMENUBAR_H

View file

@ -5,19 +5,24 @@
#ifndef MACOSNETWORKWATCHER_H
#define MACOSNETWORKWATCHER_H
#import <Network/Network.h>
#include "../ios/iosnetworkwatcher.h"
#include "networkwatcherimpl.h"
class MacOSNetworkWatcher final : public NetworkWatcherImpl {
class QString;
class MacOSNetworkWatcher final : public IOSNetworkWatcher {
public:
MacOSNetworkWatcher(QObject* parent);
~MacOSNetworkWatcher();
void initialize() override;
void start() override;
void checkInterface();
void controllerStateChanged();
private:
void* m_delegate = nullptr;
};

View file

@ -7,9 +7,10 @@
#include "logger.h"
#import <CoreWLAN/CoreWLAN.h>
#import <Network/Network.h>
namespace {
Logger logger(LOG_MACOS, "MacOSNetworkWatcher");
Logger logger("MacOSNetworkWatcher");
}
@interface MacOSNetworkWatcherDelegate : NSObject <CWEventDelegate> {
@ -37,13 +38,12 @@ Logger logger(LOG_MACOS, "MacOSNetworkWatcher");
@end
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) {
MVPN_COUNT_CTOR(MacOSNetworkWatcher);
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent) {
MZ_COUNT_CTOR(MacOSNetworkWatcher);
}
MacOSNetworkWatcher::~MacOSNetworkWatcher() {
MVPN_COUNT_DTOR(MacOSNetworkWatcher);
MZ_COUNT_DTOR(MacOSNetworkWatcher);
if (m_delegate) {
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
if (!client) {
@ -57,10 +57,6 @@ MacOSNetworkWatcher::~MacOSNetworkWatcher() {
}
}
void MacOSNetworkWatcher::initialize() {
// Nothing to do here
}
void MacOSNetworkWatcher::start() {
NetworkWatcherImpl::start();
@ -129,3 +125,4 @@ void MacOSNetworkWatcher::checkInterface() {
logger.debug() << "Secure WiFi interface";
}

View file

@ -3,10 +3,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macospingsender.h"
#include "leakdetector.h"
#include "logger.h"
#include <QSocketNotifier>
#include <arpa/inet.h>
#include <net/if.h>
@ -14,19 +10,26 @@
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <sys/errno.h>
#include <unistd.h>
#include <QSocketNotifier>
#include <QtEndian>
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger({LOG_MACOS, LOG_NETWORKING}, "MacOSPingSender");
Logger logger("MacOSPingSender");
int identifier() { return (getpid() & 0xFFFF); }
}; // namespace
MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent)
MacOSPingSender::MacOSPingSender(const QHostAddress& source, QObject* parent)
: PingSender(parent) {
MVPN_COUNT_CTOR(MacOSPingSender);
MZ_COUNT_CTOR(MacOSPingSender);
if (getuid()) {
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
@ -38,15 +41,15 @@ MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent)
return;
}
quint32 ipv4addr = INADDR_ANY;
if (!source.isNull()) {
ipv4addr = source.toIPv4Address();
}
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_len = sizeof(addr);
if (inet_aton(source.toLocal8Bit().constData(), &addr.sin_addr) == 0) {
logger.error() << "source address error";
return;
}
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4addr);
if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
logger.error() << "bind error:" << strerror(errno);
@ -59,22 +62,19 @@ MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent)
}
MacOSPingSender::~MacOSPingSender() {
MVPN_COUNT_DTOR(MacOSPingSender);
MZ_COUNT_DTOR(MacOSPingSender);
if (m_socket >= 0) {
close(m_socket);
}
}
void MacOSPingSender::sendPing(const QString& dest, quint16 sequence) {
void MacOSPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
quint32 ipv4dest = dest.toIPv4Address();
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_len = sizeof(addr);
if (inet_aton(dest.toLocal8Bit().constData(), &addr.sin_addr) == 0) {
logger.error() << "DNS lookup failed";
return;
}
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4dest);
struct icmp packet;
bzero(&packet, sizeof packet);
@ -86,6 +86,7 @@ void MacOSPingSender::sendPing(const QString& dest, quint16 sequence) {
if (sendto(m_socket, (char*)&packet, sizeof(packet), 0,
(struct sockaddr*)&addr, sizeof(addr)) != sizeof(packet)) {
logger.error() << "ping sending failed:" << strerror(errno);
emit criticalPingError();
return;
}
}

View file

@ -14,10 +14,10 @@ class MacOSPingSender final : public PingSender {
Q_DISABLE_COPY_MOVE(MacOSPingSender)
public:
MacOSPingSender(const QString& source, QObject* parent = nullptr);
MacOSPingSender(const QHostAddress& source, QObject* parent = nullptr);
~MacOSPingSender();
void sendPing(const QString& dest, quint16 sequence) override;
void sendPing(const QHostAddress& dest, quint16 sequence) override;
private slots:
void socketReady();

View file

@ -1,28 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macosstartatbootwatcher.h"
#include "leakdetector.h"
#include "logger.h"
#include "macosutils.h"
namespace {
Logger logger(LOG_MACOS, "MacOSStartAtBootWatcher");
}
MacOSStartAtBootWatcher::MacOSStartAtBootWatcher(bool startAtBoot) {
MVPN_COUNT_CTOR(MacOSStartAtBootWatcher);
logger.debug() << "StartAtBoot watcher";
MacOSUtils::enableLoginItem(startAtBoot);
}
MacOSStartAtBootWatcher::~MacOSStartAtBootWatcher() {
MVPN_COUNT_DTOR(MacOSStartAtBootWatcher);
}
void MacOSStartAtBootWatcher::startAtBootChanged(bool startAtBoot) {
logger.debug() << "StartAtBoot changed:" << startAtBoot;
MacOSUtils::enableLoginItem(startAtBoot);
}

View file

@ -1,22 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef MACOSSTARTATBOOTWATCHER_H
#define MACOSSTARTATBOOTWATCHER_H
#include <QObject>
class MacOSStartAtBootWatcher final : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(MacOSStartAtBootWatcher)
public:
explicit MacOSStartAtBootWatcher(bool startAtBoot);
~MacOSStartAtBootWatcher();
public slots:
void startAtBootChanged(bool value);
};
#endif // MACOSSTARTATBOOTWATCHER_H

View 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 MACOSSTATUSICON_H
#define MACOSSTATUSICON_H
#include <QMenu>
#include <QObject>
class MacOSStatusIcon final : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(MacOSStatusIcon)
public:
explicit MacOSStatusIcon(QObject* parent);
~MacOSStatusIcon();
public:
void setIcon(const QString& iconUrl);
void setIndicatorColor(const QColor& indicatorColor);
void setMenu(NSMenu* statusBarMenu);
void setToolTip(const QString& tooltip);
void showMessage(const QString& title, const QString& message);
};
#endif // MACOSSTATUSICON_H

View file

@ -0,0 +1,204 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macosstatusicon.h"
#include "leakdetector.h"
#include "logger.h"
#import <Cocoa/Cocoa.h>
#import <UserNotifications/UserNotifications.h>
#import <QResource>
/**
* Creates a NSStatusItem with that can hold an icon. Additionally a NSView is
* set as a subview to the button item of the status item. The view serves as
* an indicator that can be displayed in color eventhough the icon is set as a
* template. In that way we give the system control over its effective
* appearance.
*/
@interface MacOSStatusIconDelegate : NSObject
@property(assign) NSStatusItem* statusItem;
@property(assign) NSView* statusIndicator;
- (void)setIcon:(NSData*)imageData;
- (void)setIndicator;
- (void)setIndicatorColor:(NSColor*)color;
- (void)setMenu:(NSMenu*)statusBarMenu;
- (void)setToolTip:(NSString*)tooltip;
@end
@implementation MacOSStatusIconDelegate
/**
* Initializes and sets the status item and indicator objects.
*
* @return An instance of MacOSStatusIconDelegate.
*/
- (id)init {
self = [super init];
// Create status item
self.statusItem =
[[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
self.statusItem.visible = true;
// Add the indicator as a subview
[self setIndicator];
return self;
}
/**
* Sets the image for the status icon.
*
* @param iconPath The data for the icon image.
*/
- (void)setIcon:(NSData*)imageData {
NSImage* image = [[NSImage alloc] initWithData:imageData];
[image setTemplate:true];
[self.statusItem.button setImage:image];
[image release];
}
/**
* Adds status indicator as a subview to the status item button.
*/
- (void)setIndicator {
float viewHeight = NSHeight([self.statusItem.button bounds]);
float dotSize = viewHeight * 0.35;
float dotOrigin = (viewHeight - dotSize) * 0.8;
NSView* dot = [[NSView alloc] initWithFrame:NSMakeRect(dotOrigin, dotOrigin, dotSize, dotSize)];
self.statusIndicator = dot;
self.statusIndicator.wantsLayer = true;
self.statusIndicator.layer.cornerRadius = dotSize * 0.5;
[self.statusItem.button addSubview:self.statusIndicator];
[dot release];
}
/**
* Sets the color if the indicator.
*
* @param color The indicator background color.
*/
- (void)setIndicatorColor:(NSColor*)color {
if (self.statusIndicator) {
self.statusIndicator.layer.backgroundColor = color.CGColor;
}
}
/**
* Sets the status bar menu to the status item.
*
* @param statusBarMenu The menu object that is passed from QT.
*/
- (void)setMenu:(NSMenu*)statusBarMenu {
[self.statusItem setMenu:statusBarMenu];
}
/**
* Sets the tooltip string for the status item.
*
* @param tooltip The tooltip string.
*/
- (void)setToolTip:(NSString*)tooltip {
[self.statusItem.button setToolTip:tooltip];
}
@end
namespace {
Logger logger("MacOSStatusIcon");
MacOSStatusIconDelegate* m_statusBarIcon = nullptr;
}
MacOSStatusIcon::MacOSStatusIcon(QObject* parent) : QObject(parent) {
MZ_COUNT_CTOR(MacOSStatusIcon);
logger.debug() << "Register delegate";
Q_ASSERT(!m_statusBarIcon);
m_statusBarIcon = [[MacOSStatusIconDelegate alloc] init];
}
MacOSStatusIcon::~MacOSStatusIcon() {
MZ_COUNT_DTOR(MacOSStatusIcon);
logger.debug() << "Remove delegate";
Q_ASSERT(m_statusBarIcon);
[static_cast<MacOSStatusIconDelegate*>(m_statusBarIcon) dealloc];
m_statusBarIcon = nullptr;
}
void MacOSStatusIcon::setIcon(const QString& iconPath) {
logger.debug() << "Set icon" << iconPath;
QResource imageResource = QResource(iconPath);
Q_ASSERT(imageResource.isValid());
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()];
}
void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
logger.debug() << "Set indicator color";
if (!indicatorColor.isValid()) {
[m_statusBarIcon setIndicatorColor:[NSColor clearColor]];
return;
}
NSColor* color = [NSColor colorWithCalibratedRed:indicatorColor.red() / 255.0f
green:indicatorColor.green() / 255.0f
blue:indicatorColor.blue() / 255.0f
alpha:indicatorColor.alpha() / 255.0f];
[m_statusBarIcon setIndicatorColor:color];
}
void MacOSStatusIcon::setMenu(NSMenu* statusBarMenu) {
logger.debug() << "Set menu";
[m_statusBarIcon setMenu:statusBarMenu];
}
void MacOSStatusIcon::setToolTip(const QString& tooltip) {
logger.debug() << "Set tooltip";
[m_statusBarIcon setToolTip:tooltip.toNSString()];
}
void MacOSStatusIcon::showMessage(const QString& title, const QString& message) {
logger.debug() << "Show message";
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
// This is a no-op is authorization has been granted.
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError* _Nullable error) {
if (error) {
// Note: This error may happen if the application is not signed.
NSLog(@"Error asking for permission to send notifications %@", error);
return;
}
}];
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = [title.toNSString() autorelease];
content.body = [message.toNSString() autorelease];
content.sound = [UNNotificationSound defaultSound];
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
content:content
trigger:trigger];
[center addNotificationRequest:request
withCompletionHandler:^(NSError* _Nullable error) {
if (error) {
logger.error() << "Local Notification failed" << error;
}
}];
}

View file

@ -10,14 +10,19 @@
class MacOSUtils final {
public:
static NSString* appId();
static QString computerName();
static void enableLoginItem(bool startAtBoot);
static void setDockClickHandler();
static void setStatusBarTextColor();
static void hideDockIcon();
static void showDockIcon();
static void patchNSStatusBarSetImageForBigSur();
};
#endif // MACOSUTILS_H

View file

@ -4,20 +4,27 @@
#include "macosutils.h"
#include "logger.h"
#include "models/helpmodel.h"
#include "qmlengineholder.h"
#include <objc/message.h>
#include <objc/objc.h>
#include <QFile>
#include <QMenuBar>
#import <Cocoa/Cocoa.h>
#import <ServiceManagement/ServiceManagement.h>
namespace {
Logger logger(LOG_MACOS, "MacOSUtils");
Logger logger("MacOSUtils");
}
// static
NSString* MacOSUtils::appId() {
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
if (!appId) {
// Fallback. When an unsigned/un-notarized app is executed in
// command-line mode, it could fail the fetching of its own bundle id.
appId = @"org.amnezia.AmneziaVPN";
}
return appId;
}
// static
@ -30,7 +37,9 @@ QString MacOSUtils::computerName() {
void MacOSUtils::enableLoginItem(bool startAtBoot) {
logger.debug() << "Enabling login-item";
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
NSString* appId = MacOSUtils::appId();
Q_ASSERT(appId);
NSString* loginItemAppId =
QString("%1.login-item").arg(QString::fromNSString(appId)).toNSString();
CFStringRef cfs = (__bridge CFStringRef)loginItemAppId;
@ -46,7 +55,8 @@ bool dockClickHandler(id self, SEL cmd, ...) {
Q_UNUSED(cmd);
logger.debug() << "Dock icon clicked.";
QmlEngineHolder::instance()->showWindow();
//TODO IMPL FOR AMNEZIA
//QmlEngineHolder::instance()->showWindow();
return FALSE;
}
@ -89,3 +99,102 @@ void MacOSUtils::hideDockIcon() {
void MacOSUtils::showDockIcon() {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
}
/**
* Replace the setImage method on NSStatusBarButton with a method that scales
* images proportionally before setting.
*
* The reason for this is that there is a bug in Qt 5.15 that causes status bar
* icons to be displayed larger than UI recommendations, and out of proportion
* on displays with a device pixel ratio greater than 1 (MacOS Big Sur only).
* This bug will not be fixed in Qt open source versions, so we have to resort
* to a hack that exchanges the implementation of a method on NSStatusBarButton
* with one that correctly scales the icon.
*
* Original bug (and sample implementation):
* https://bugreports.qt.io/browse/QTBUG-88600
*/
void MacOSUtils::patchNSStatusBarSetImageForBigSur() {
Method original = class_getInstanceMethod([NSStatusBarButton class], @selector(setImage:));
Method patched = class_getInstanceMethod([NSStatusBarButton class], @selector(setImagePatched:));
method_exchangeImplementations(original, patched);
}
@interface NSImageScalingHelper : NSObject
/**
* Create a proportionally scaled image according to the given target size.
*
* @param sourceImage The original image to be scaled.
* @param targetSize The required size of the image.
* @return A scaled image.
*/
+ (NSImage*)imageByScaling:(NSImage*)sourceImage size:(NSSize)targetSize;
@end
@implementation NSImageScalingHelper
+ (NSImage*)imageByScaling:(NSImage*)sourceImage size:(NSSize)targetSize {
NSImage* newImage = nil;
if ([sourceImage isValid]) {
NSSize sourceSize = [sourceImage size];
if (sourceSize.width != 0.0 && sourceSize.height != 0.0) {
float scaleFactor = 0.0;
float scaledWidth = targetSize.width;
float scaledHeight = targetSize.height;
NSPoint thumbnailPoint = NSZeroPoint;
if (NSEqualSizes(sourceSize, targetSize) == NO) {
float widthFactor = targetSize.width / sourceSize.width;
float heightFactor = targetSize.height / sourceSize.height;
if (widthFactor < heightFactor) {
scaleFactor = widthFactor;
} else {
scaleFactor = heightFactor;
}
scaledWidth = sourceSize.width * scaleFactor;
scaledHeight = sourceSize.height * scaleFactor;
if (widthFactor < heightFactor) {
thumbnailPoint.y = (targetSize.height - scaledHeight) * 0.5;
} else {
thumbnailPoint.x = (targetSize.width - scaledWidth) * 0.5;
}
}
newImage = [[NSImage alloc] initWithSize:targetSize];
[newImage lockFocus];
NSRect thumbnailRect;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect
fromRect:NSZeroRect
operation:NSCompositingOperationSourceOver
fraction:1.0];
[newImage unlockFocus];
[newImage setTemplate:[sourceImage isTemplate]];
}
}
return [newImage autorelease];
}
@end
@implementation NSStatusBarButton (Swizzle)
- (void)setImagePatched:(NSImage*)image {
NSImage* img = image;
if (image != nil) {
int thickness = [[NSStatusBar systemStatusBar] thickness];
img = [NSImageScalingHelper imageByScaling:image size:NSMakeSize(thickness, thickness)];
}
[self setImagePatched:img];
}
@end