mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
WireGuard for MacOS (#248)
* WireGuard for MacOS * Fix openvpn block-outside-dns
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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("", "", "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user