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,89 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef CONTROLLERIMPL_H
#define CONTROLLERIMPL_H
#include <QDateTime>
#include <QObject>
#include <functional>
class Keys;
class Device;
class Server;
class QDateTime;
class IPAddress;
class QHostAddress;
// This object is allocated when the VPN is about to be activated.
// It's kept alive, basically forever, except in these scenarios, in which it's
// recreated:
// - the user does a logout
// - there is an authentication falure
class ControllerImpl : public QObject {
Q_OBJECT
public:
ControllerImpl() = default;
virtual ~ControllerImpl() = default;
// This method is called to initialize the controller. The initialization
// is completed when the signal "initialized" is emitted.
virtual void initialize(const Device* device, const Keys* keys) = 0;
// This method is called when the VPN client needs to activate the VPN
// tunnel. It's called only at the end of the initialization process. When
// this method is called, the VPN client is in "connecting" state. This
// state terminates when the "connected" (or the "disconnected") signal is
// received.
virtual void activate(const QJsonObject& config) = 0;
// This method terminates the VPN tunnel. The VPN client is in
// "disconnecting" state until the "disconnected" signal is received.
virtual void deactivate() = 0;
// This method is used to retrieve the VPN tunnel status (mainly the number
// of bytes sent and received). It's called always when the VPN tunnel is
// active.
virtual void checkStatus() = 0;
// This method is used to retrieve the logs from the backend service. Use
// the callback to report logs when available.
virtual void getBackendLogs(
std::function<void(const QString& logs)>&& callback) = 0;
// Cleanup the backend logs.
virtual void cleanupBackendLogs() = 0;
// Whether the controller supports multihop
virtual bool multihopSupported() { return false; }
virtual bool silentServerSwitchingSupported() const { return true; }
signals:
// This signal is emitted when the controller is initialized. Note that the
// VPN tunnel can be already active. In this case, "connected" should be set
// to true and the "connectionDate" should be set to the activation date if
// known.
// If "status" is set to false, the backend service is considered unavailable.
void initialized(bool status, bool connected,
const QDateTime& connectionDate);
// These 2 signals can be dispatched at any time.
void connected(const QString& pubkey,
const QDateTime& connectionTimestamp = QDateTime());
void disconnected();
// This method should be emitted after a checkStatus() call.
// "serverIpv4Gateway" is the current VPN tunnel gateway.
// "deviceIpv4Address" is the address of the VPN client.
// "txBytes" and "rxBytes" contain the number of transmitted and received
// bytes since the last statusUpdated signal.
void statusUpdated(const QString& serverIpv4Gateway,
const QString& deviceIpv4Address, uint64_t txBytes,
uint64_t rxBytes);
};
#endif // CONTROLLERIMPL_H

View file

@ -0,0 +1,117 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "dnspingsender.h"
#include <string.h>
#include <QNetworkDatagram>
#include <QtEndian>
#include "leakdetector.h"
#include "logger.h"
constexpr const quint16 DNS_PORT = 53;
// A quick and dirty DNS Header structure definition from RFC1035,
// Section 4.1.1: Header Section format.
struct dnsHeader {
quint16 id;
quint16 flags;
quint16 qdcount;
quint16 ancount;
quint16 nscount;
quint16 arcount;
};
// Bit definitions for the DNS flags field.
#define DNS_FLAG_QR 0x8000
#define DNS_FLAG_OPCODE 0x7800
#define DNS_FLAG_OPCODE_QUERY (0x0 << 11)
#define DNS_FLAG_OPCODE_IQUERY (0x1 << 11)
#define DNS_FLAG_OPCODE_STATUS (0x2 << 11)
#define DNS_FLAG_AA 0x0400
#define DNS_FLAG_TC 0x0200
#define DNS_FLAG_RD 0x0100
#define DNS_FLAG_RA 0x0080
#define DNS_FLAG_Z 0x0070
#define DNS_FLAG_RCODE 0x000F
#define DNS_FLAG_RCODE_NO_ERROR (0x0 << 0)
#define DNS_FLAG_RCODE_FORMAT_ERROR (0x1 << 0)
#define DNS_FLAG_RCODE_SERVER_FAILURE (0x2 << 0)
#define DNS_FLAG_RCODE_NAME_ERROR (0x3 << 0)
#define DNS_FLAG_RCODE_NOT_IMPLEMENTED (0x4 << 0)
#define DNS_FLAG_RCODE_REFUSED (0x5 << 0)
namespace {
Logger logger("DnsPingSender");
}
DnsPingSender::DnsPingSender(const QHostAddress& source, QObject* parent)
: PingSender(parent) {
MZ_COUNT_CTOR(DnsPingSender);
if (source.isNull()) {
m_socket.bind();
} else {
m_socket.bind(source);
}
connect(&m_socket, &QUdpSocket::readyRead, this, &DnsPingSender::readData);
}
DnsPingSender::~DnsPingSender() { MZ_COUNT_DTOR(DnsPingSender); }
void DnsPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
QByteArray packet;
// Assemble a DNS query header.
struct dnsHeader header;
memset(&header, 0, sizeof(header));
header.id = qToBigEndian<quint16>(sequence);
header.flags = qToBigEndian<quint16>(DNS_FLAG_OPCODE_QUERY);
header.qdcount = qToBigEndian<quint16>(1);
header.ancount = 0;
header.nscount = 0;
header.arcount = 0;
packet.append(reinterpret_cast<char*>(&header), sizeof(header));
// Add a query for the root nameserver: {<root>, type A, class IN}
const char query[] = {0x00, 0x00, 0x01, 0x00, 0x01};
packet.append(query, sizeof(query));
// Send the datagram.
m_socket.writeDatagram(packet, dest, DNS_PORT);
}
void DnsPingSender::readData() {
while (m_socket.hasPendingDatagrams()) {
QNetworkDatagram reply = m_socket.receiveDatagram();
if (!reply.isValid()) {
break;
}
// Extract the header from the DNS response.
QByteArray payload = reply.data();
struct dnsHeader header;
if (payload.length() < static_cast<int>(sizeof(header))) {
logger.debug() << "Received bogus DNS reply: truncated header";
continue;
}
memcpy(&header, payload.constData(), sizeof(header));
// Perfom some checks to ensure this is the reply we were expecting.
quint16 flags = qFromBigEndian<quint16>(header.flags);
if ((flags & DNS_FLAG_QR) == 0) {
logger.debug() << "Received bogus DNS reply: QR == query";
continue;
}
if ((flags & DNS_FLAG_OPCODE) != DNS_FLAG_OPCODE_QUERY) {
logger.debug() << "Received bogus DNS reply: OPCODE != query";
continue;
}
emit recvPing(qFromBigEndian<quint16>(header.id));
}
}

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/. */
#ifndef DNSPINGSENDER_H
#define DNSPINGSENDER_H
#include <QUdpSocket>
#include "pingsender.h"
class DnsPingSender final : public PingSender {
Q_OBJECT
Q_DISABLE_COPY_MOVE(DnsPingSender)
public:
DnsPingSender(const QHostAddress& source, QObject* parent = nullptr);
~DnsPingSender();
void sendPing(const QHostAddress& dest, quint16 sequence) override;
private:
void readData();
private:
QUdpSocket m_socket;
};
#endif // DNSPINGSENDER_H

View file

@ -0,0 +1,384 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "protocols/protocols_defs.h"
#include "localsocketcontroller.h"
#include <QDir>
#include <QFileInfo>
#include <QHostAddress>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QStandardPaths>
#include "ipaddress.h"
#include "leakdetector.h"
#include "logger.h"
#include "models/server.h"
// How many times do we try to reconnect.
constexpr int MAX_CONNECTION_RETRY = 10;
// How long do we wait between one try and the next one.
constexpr int CONNECTION_RETRY_TIMER_MSEC = 500;
namespace {
Logger logger("LocalSocketController");
}
LocalSocketController::LocalSocketController() {
MZ_COUNT_CTOR(LocalSocketController);
m_socket = new QLocalSocket(this);
connect(m_socket, &QLocalSocket::connected, this,
&LocalSocketController::daemonConnected);
connect(m_socket, &QLocalSocket::disconnected, this,
&LocalSocketController::disconnected);
connect(m_socket, &QLocalSocket::errorOccurred, this,
&LocalSocketController::errorOccurred);
connect(m_socket, &QLocalSocket::readyRead, this,
&LocalSocketController::readData);
m_initializingTimer.setSingleShot(true);
connect(&m_initializingTimer, &QTimer::timeout, this,
&LocalSocketController::initializeInternal);
}
LocalSocketController::~LocalSocketController() {
MZ_COUNT_DTOR(LocalSocketController);
}
void LocalSocketController::errorOccurred(
QLocalSocket::LocalSocketError error) {
logger.error() << "Error occurred:" << error;
if (m_daemonState == eInitializing) {
if (m_initializingRetry++ < MAX_CONNECTION_RETRY) {
m_initializingTimer.start(CONNECTION_RETRY_TIMER_MSEC);
return;
}
emit initialized(false, false, QDateTime());
}
qCritical() << "ControllerError";
disconnectInternal();
}
void LocalSocketController::disconnectInternal() {
// We're still eReady as the Deamon is alive
// and can make a new connection.
m_daemonState = eReady;
m_initializingRetry = 0;
m_initializingTimer.stop();
emit disconnected();
}
void LocalSocketController::initialize(const Device* device, const Keys* keys) {
logger.debug() << "Initializing";
Q_UNUSED(device);
Q_UNUSED(keys);
Q_ASSERT(m_daemonState == eUnknown);
m_initializingRetry = 0;
initializeInternal();
}
void LocalSocketController::initializeInternal() {
m_daemonState = eInitializing;
#ifdef MZ_WINDOWS
QString path = "\\\\.\\pipe\\amneziavpn";
#else
QString path = "/var/run/amneziavpn/daemon.socket";
if (!QFileInfo::exists(path)) {
path = "/tmp/amneziavpn.socket";
}
#endif
logger.debug() << "Connecting to:" << path;
m_socket->connectToServer(path);
}
void LocalSocketController::daemonConnected() {
logger.debug() << "Daemon connected";
Q_ASSERT(m_daemonState == eInitializing);
checkStatus();
}
void LocalSocketController::activate(const QJsonObject &rawConfig) {
qDebug() << rawConfig;
QJsonObject wgConfig = rawConfig.value("wireguard_config_data").toObject();
QJsonObject json;
json.insert("type", "activate");
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
json.insert("deviceIpv6Address", "dead::1");
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn()));
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway()));
json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1));
QJsonArray jsAllowedIPAddesses;
QJsonObject range_ipv4;
range_ipv4.insert("address", "0.0.0.0");
range_ipv4.insert("range", 0);
range_ipv4.insert("isIpv6", false);
jsAllowedIPAddesses.append(range_ipv4);
QJsonObject range_ipv6;
range_ipv6.insert("address", "::");
range_ipv6.insert("range", 0);
range_ipv6.insert("isIpv6", true);
jsAllowedIPAddesses.append(range_ipv6);
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
QJsonArray jsExcludedAddresses;
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
json.insert("excludedAddresses", jsExcludedAddresses);
// QJsonArray splitTunnelApps;
// for (const auto& uri : hop.m_vpnDisabledApps) {
// splitTunnelApps.append(QJsonValue(uri));
// }
// json.insert("vpnDisabledApps", splitTunnelApps);
write(json);
}
void LocalSocketController::deactivate() {
logger.debug() << "Deactivating";
if (m_daemonState != eReady) {
logger.debug() << "No disconnect, controller is not ready";
emit disconnected();
return;
}
QJsonObject json;
json.insert("type", "deactivate");
write(json);
}
void LocalSocketController::checkStatus() {
logger.debug() << "Check status";
if (m_daemonState == eReady || m_daemonState == eInitializing) {
Q_ASSERT(m_socket);
QJsonObject json;
json.insert("type", "status");
write(json);
}
}
void LocalSocketController::getBackendLogs(
std::function<void(const QString&)>&& a_callback) {
logger.debug() << "Backend logs";
if (m_logCallback) {
m_logCallback("");
m_logCallback = nullptr;
}
if (m_daemonState != eReady) {
std::function<void(const QString&)> callback = a_callback;
callback("");
return;
}
m_logCallback = std::move(a_callback);
QJsonObject json;
json.insert("type", "logs");
write(json);
}
void LocalSocketController::cleanupBackendLogs() {
logger.debug() << "Cleanup logs";
if (m_logCallback) {
m_logCallback("");
m_logCallback = nullptr;
}
if (m_daemonState != eReady) {
return;
}
QJsonObject json;
json.insert("type", "cleanlogs");
write(json);
}
void LocalSocketController::readData() {
logger.debug() << "Reading";
Q_ASSERT(m_socket);
Q_ASSERT(m_daemonState == eInitializing || m_daemonState == eReady);
QByteArray input = m_socket->readAll();
m_buffer.append(input);
while (true) {
int pos = m_buffer.indexOf("\n");
if (pos == -1) {
break;
}
QByteArray line = m_buffer.left(pos);
m_buffer.remove(0, pos + 1);
QByteArray command(line);
command = command.trimmed();
if (command.isEmpty()) {
continue;
}
parseCommand(command);
}
}
void LocalSocketController::parseCommand(const QByteArray& command) {
QJsonDocument json = QJsonDocument::fromJson(command);
if (!json.isObject()) {
logger.error() << "Invalid JSON - object expected";
return;
}
QJsonObject obj = json.object();
QJsonValue typeValue = obj.value("type");
if (!typeValue.isString()) {
logger.error() << "Invalid JSON - no type";
return;
}
QString type = typeValue.toString();
logger.debug() << "Parse command:" << type;
if (m_daemonState == eInitializing && type == "status") {
m_daemonState = eReady;
QJsonValue connected = obj.value("connected");
if (!connected.isBool()) {
logger.error() << "Invalid JSON for status - connected expected";
return;
}
QDateTime datetime;
if (connected.toBool()) {
QJsonValue date = obj.value("date");
if (!date.isString()) {
logger.error() << "Invalid JSON for status - date expected";
return;
}
datetime = QDateTime::fromString(date.toString());
if (!datetime.isValid()) {
logger.error() << "Invalid JSON for status - date is invalid";
return;
}
}
emit initialized(true, connected.toBool(), datetime);
return;
}
if (m_daemonState != eReady) {
logger.error() << "Unexpected command";
return;
}
if (type == "status") {
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
if (!serverIpv4Gateway.isString()) {
logger.error() << "Unexpected serverIpv4Gateway value";
return;
}
QJsonValue deviceIpv4Address = obj.value("deviceIpv4Address");
if (!deviceIpv4Address.isString()) {
logger.error() << "Unexpected deviceIpv4Address value";
return;
}
QJsonValue txBytes = obj.value("txBytes");
if (!txBytes.isDouble()) {
logger.error() << "Unexpected txBytes value";
return;
}
QJsonValue rxBytes = obj.value("rxBytes");
if (!rxBytes.isDouble()) {
logger.error() << "Unexpected rxBytes value";
return;
}
emit statusUpdated(serverIpv4Gateway.toString(),
deviceIpv4Address.toString(), txBytes.toDouble(),
rxBytes.toDouble());
return;
}
if (type == "disconnected") {
disconnectInternal();
return;
}
if (type == "connected") {
QJsonValue pubkey = obj.value("pubkey");
if (!pubkey.isString()) {
logger.error() << "Unexpected pubkey value";
return;
}
logger.debug() << "Handshake completed with:"
<< pubkey.toString();
emit connected(pubkey.toString());
return;
}
if (type == "backendFailure") {
qCritical() << "backendFailure";
return;
}
if (type == "logs") {
// We don't care if we are not waiting for logs.
if (!m_logCallback) {
return;
}
QJsonValue logs = obj.value("logs");
m_logCallback(logs.isString() ? logs.toString().replace("|", "\n")
: QString());
m_logCallback = nullptr;
return;
}
logger.warning() << "Invalid command received:" << command;
}
void LocalSocketController::write(const QJsonObject& json) {
Q_ASSERT(m_socket);
m_socket->write(QJsonDocument(json).toJson(QJsonDocument::Compact));
m_socket->write("\n");
m_socket->flush();
}

View file

@ -0,0 +1,67 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LOCALSOCKETCONTROLLER_H
#define LOCALSOCKETCONTROLLER_H
#include <QHostAddress>
#include <QLocalSocket>
#include <QTimer>
#include <functional>
#include "controllerimpl.h"
class QJsonObject;
class LocalSocketController final : public ControllerImpl {
Q_DISABLE_COPY_MOVE(LocalSocketController)
public:
LocalSocketController();
~LocalSocketController();
void initialize(const Device* device, const Keys* keys) override;
void activate(const QJsonObject& rawConfig) override;
void deactivate() override;
void checkStatus() override;
void getBackendLogs(std::function<void(const QString&)>&& callback) override;
void cleanupBackendLogs() override;
bool multihopSupported() override { return true; }
private:
void initializeInternal();
void disconnectInternal();
void daemonConnected();
void errorOccurred(QLocalSocket::LocalSocketError socketError);
void readData();
void parseCommand(const QByteArray& command);
void write(const QJsonObject& json);
private:
enum {
eUnknown,
eInitializing,
eReady,
eDisconnected,
} m_daemonState = eUnknown;
QLocalSocket* m_socket = nullptr;
QByteArray m_buffer;
std::function<void(const QString&)> m_logCallback = nullptr;
QTimer m_initializingTimer;
uint32_t m_initializingRetry = 0;
};
#endif // LOCALSOCKETCONTROLLER_H

View file

@ -0,0 +1,209 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "server.h"
#include <QDateTime>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QRandomGenerator>
#include "leakdetector.h"
Server::Server() { MZ_COUNT_CTOR(Server); }
Server::Server(const QString& countryCode, const QString& cityName) {
MZ_COUNT_CTOR(Server);
m_countryCode = countryCode;
m_cityName = cityName;
}
Server::Server(const Server& other) {
MZ_COUNT_CTOR(Server);
*this = other;
}
Server& Server::operator=(const Server& other) {
if (this == &other) return *this;
m_hostname = other.m_hostname;
m_ipv4AddrIn = other.m_ipv4AddrIn;
m_ipv4Gateway = other.m_ipv4Gateway;
m_ipv6AddrIn = other.m_ipv6AddrIn;
m_ipv6Gateway = other.m_ipv6Gateway;
m_portRanges = other.m_portRanges;
m_publicKey = other.m_publicKey;
m_weight = other.m_weight;
m_socksName = other.m_socksName;
m_multihopPort = other.m_multihopPort;
m_countryCode = other.m_countryCode;
m_cityName = other.m_cityName;
return *this;
}
Server::~Server() { MZ_COUNT_DTOR(Server); }
bool Server::fromJson(const QJsonObject& obj) {
// Reset.
m_hostname = "";
QJsonValue hostname = obj.value("hostname");
if (!hostname.isString()) {
return false;
}
QJsonValue ipv4AddrIn = obj.value("ipv4_addr_in");
if (!ipv4AddrIn.isString()) {
return false;
}
QJsonValue ipv4Gateway = obj.value("ipv4_gateway");
if (!ipv4Gateway.isString()) {
return false;
}
QJsonValue ipv6AddrIn = obj.value("ipv6_addr_in");
// If this object comes from the IOS migration, the ipv6_addr_in is missing.
QJsonValue ipv6Gateway = obj.value("ipv6_gateway");
if (!ipv6Gateway.isString()) {
return false;
}
QJsonValue publicKey = obj.value("public_key");
if (!publicKey.isString()) {
return false;
}
QJsonValue weight = obj.value("weight");
if (!weight.isDouble()) {
return false;
}
QJsonValue portRanges = obj.value("port_ranges");
if (!portRanges.isArray()) {
return false;
}
// optional properties.
QJsonValue socks5_name = obj.value("socks5_name");
QJsonValue multihop_port = obj.value("multihop_port");
QList<QPair<uint32_t, uint32_t>> prList;
QJsonArray portRangesArray = portRanges.toArray();
for (const QJsonValue& portRangeValue : portRangesArray) {
if (!portRangeValue.isArray()) {
return false;
}
QJsonArray port = portRangeValue.toArray();
if (port.count() != 2) {
return false;
}
QJsonValue a = port.at(0);
if (!a.isDouble()) {
return false;
}
QJsonValue b = port.at(1);
if (!b.isDouble()) {
return false;
}
prList.append(QPair<uint32_t, uint32_t>(a.toInt(), b.toInt()));
}
m_hostname = hostname.toString();
m_ipv4AddrIn = ipv4AddrIn.toString();
m_ipv4Gateway = ipv4Gateway.toString();
m_ipv6AddrIn = ipv6AddrIn.toString();
m_ipv6Gateway = ipv6Gateway.toString();
m_portRanges.swap(prList);
m_publicKey = publicKey.toString();
m_weight = weight.toInt();
m_socksName = socks5_name.toString();
m_multihopPort = multihop_port.toInt();
return true;
}
bool Server::fromMultihop(const Server& exit, const Server& entry) {
m_hostname = exit.m_hostname;
m_ipv4Gateway = exit.m_ipv4Gateway;
m_ipv6Gateway = exit.m_ipv6Gateway;
m_publicKey = exit.m_publicKey;
m_socksName = exit.m_socksName;
m_multihopPort = exit.m_multihopPort;
m_ipv4AddrIn = entry.m_ipv4AddrIn;
m_ipv6AddrIn = entry.m_ipv6AddrIn;
return forcePort(exit.m_multihopPort);
}
bool Server::forcePort(uint32_t port) {
m_portRanges.clear();
m_portRanges.append(QPair<uint32_t, uint32_t>(port, port));
return true;
}
// static
const Server& Server::weightChooser(const QList<Server>& servers) {
static const Server emptyServer;
Q_ASSERT(!emptyServer.initialized());
if (servers.isEmpty()) {
return emptyServer;
}
uint32_t weightSum = 0;
for (const Server& server : servers) {
weightSum += server.weight();
}
quint32 r = QRandomGenerator::global()->generate() % (weightSum + 1);
for (const Server& server : servers) {
if (server.weight() >= r) {
return server;
}
r -= server.weight();
}
// This should not happen.
Q_ASSERT(false);
return emptyServer;
}
uint32_t Server::choosePort() const {
if (m_portRanges.isEmpty()) {
return 0;
}
// Count the total number of potential ports.
quint32 length = 0;
for (const QPair<uint32_t, uint32_t>& range : m_portRanges) {
Q_ASSERT(range.first <= range.second);
length += range.second - range.first + 1;
}
Q_ASSERT(length < 65536);
Q_ASSERT(length > 0);
// Pick a port at random.
quint32 r = QRandomGenerator::global()->generate() % length;
quint32 port = 0;
for (const QPair<uint32_t, uint32_t>& range : m_portRanges) {
if (r <= (range.second - range.first)) {
port = r + range.first;
break;
}
r -= (range.second - range.first + 1);
}
Q_ASSERT(port != 0);
return port;
}

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/. */
#ifndef SERVER_H
#define SERVER_H
#include <QList>
#include <QPair>
#include <QString>
class QJsonObject;
class Server final {
public:
Server();
Server(const QString& countryCode, const QString& cityName);
Server(const Server& other);
Server& operator=(const Server& other);
~Server();
[[nodiscard]] bool fromJson(const QJsonObject& obj);
bool fromMultihop(const Server& exit, const Server& entry);
static const Server& weightChooser(const QList<Server>& servers);
bool initialized() const { return !m_hostname.isEmpty(); }
const QString& hostname() const { return m_hostname; }
const QString& ipv4AddrIn() const { return m_ipv4AddrIn; }
const QString& ipv4Gateway() const { return m_ipv4Gateway; }
const QString& ipv6AddrIn() const { return m_ipv6AddrIn; }
const QString& ipv6Gateway() const { return m_ipv6Gateway; }
const QString& publicKey() const { return m_publicKey; }
const QString& socksName() const { return m_socksName; }
uint32_t weight() const { return m_weight; }
uint32_t choosePort() const;
uint32_t multihopPort() const { return m_multihopPort; }
const QString& countryCode() const { return m_countryCode; }
const QString& cityName() const { return m_cityName; }
bool forcePort(uint32_t port);
bool operator==(const Server& other) const {
return m_publicKey == other.m_publicKey;
}
// Allow checking against QString, so we can easily search a QList<Server> for
// a public key.
bool operator==(const QString& otherPublicKey) const {
return m_publicKey == otherPublicKey;
}
private:
QString m_hostname;
QString m_ipv4AddrIn;
QString m_ipv4Gateway;
QString m_ipv6AddrIn;
QString m_ipv6Gateway;
QList<QPair<uint32_t, uint32_t>> m_portRanges;
QString m_publicKey;
QString m_socksName;
uint32_t m_weight = 0;
uint32_t m_multihopPort = 0;
QString m_countryCode;
QString m_cityName;
};
#endif // SERVER_H

View file

@ -0,0 +1,106 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "networkwatcher.h"
#include <QMetaEnum>
#include "leakdetector.h"
#include "logger.h"
#include "networkwatcherimpl.h"
#include "platforms/dummy/dummynetworkwatcher.h"
#ifdef MZ_WINDOWS
//# include "platforms/windows/windowsnetworkwatcher.h"
#endif
#ifdef MZ_LINUX
//# include "platforms/linux/linuxnetworkwatcher.h"
#endif
#ifdef MZ_MACOS
# include "platforms/macos/macosnetworkwatcher.h"
#endif
#ifdef MZ_WASM
# include "platforms/wasm/wasmnetworkwatcher.h"
#endif
#ifdef MZ_ANDROID
# include "platforms/android/androidnetworkwatcher.h"
#endif
#ifdef MZ_IOS
# include "platforms/ios/iosnetworkwatcher.h"
#endif
// How often we notify the same unsecured network
#ifndef UNIT_TEST
constexpr uint32_t NETWORK_WATCHER_TIMER_MSEC = 20000;
#endif
namespace {
Logger logger("NetworkWatcher");
}
NetworkWatcher::NetworkWatcher() { MZ_COUNT_CTOR(NetworkWatcher); }
NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); }
void NetworkWatcher::initialize() {
logger.debug() << "Initialize";
#if defined(MZ_WINDOWS)
//m_impl = new WindowsNetworkWatcher(this);
#elif defined(MZ_LINUX)
//m_impl = new LinuxNetworkWatcher(this);
#elif defined(MZ_MACOS)
m_impl = new MacOSNetworkWatcher(this);
#elif defined(MZ_WASM)
m_impl = new WasmNetworkWatcher(this);
#elif defined(MZ_ANDROID)
m_impl = new AndroidNetworkWatcher(this);
#elif defined(MZ_IOS)
m_impl = new IOSNetworkWatcher(this);
#else
m_impl = new DummyNetworkWatcher(this);
#endif
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
&NetworkWatcher::unsecuredNetwork);
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
&NetworkWatcher::networkChange);
m_impl->initialize();
//TODO IMPL FOR AMNEZIA
}
void NetworkWatcher::settingsChanged() {
//TODO IMPL FOR AMNEZIA
if (m_active) {
logger.debug()
<< "Starting Network Watcher; Reporting of Unsecured Networks: "
<< m_reportUnsecuredNetwork;
m_impl->start();
} else {
logger.debug() << "Stopping Network Watcher";
m_impl->stop();
}
}
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
const QString& networkId) {
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
<< "id:" << logger.sensitive(networkId);
//TODO IMPL FOR AMNEZIA
}
QString NetworkWatcher::getCurrentTransport() {
auto type = m_impl->getTransportType();
QMetaEnum metaEnum = QMetaEnum::fromType<NetworkWatcherImpl::TransportType>();
return QString(metaEnum.valueToKey(type))
.remove("TransportType_", Qt::CaseSensitive);
}

View file

@ -0,0 +1,49 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef NETWORKWATCHER_H
#define NETWORKWATCHER_H
#include <QElapsedTimer>
#include <QMap>
#include <QObject>
class NetworkWatcherImpl;
// This class watches for network changes to detect unsecured wifi.
class NetworkWatcher final : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(NetworkWatcher)
public:
NetworkWatcher();
~NetworkWatcher();
void initialize();
// public for the inspector.
void unsecuredNetwork(const QString& networkName, const QString& networkId);
QString getCurrentTransport();
signals:
void networkChange();
private:
void settingsChanged();
private:
bool m_active = false;
bool m_reportUnsecuredNetwork = false;
// Platform-specific implementation.
NetworkWatcherImpl* m_impl = nullptr;
QMap<QString, QElapsedTimer> m_networks;
// This is used to connect NotificationHandler lazily.
bool m_firstNotification = true;
};
#endif // NETWORKWATCHER_H

View file

@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef NETWORKWATCHERIMPL_H
#define NETWORKWATCHERIMPL_H
#include <QObject>
class NetworkWatcherImpl : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(NetworkWatcherImpl)
public:
NetworkWatcherImpl(QObject* parent) : QObject(parent) {}
virtual ~NetworkWatcherImpl() = default;
virtual void initialize() = 0;
virtual void start() { m_active = true; }
virtual void stop() { m_active = false; }
bool isActive() const { return m_active; }
enum TransportType {
TransportType_Unknown = 0,
TransportType_Ethernet = 1,
TransportType_WiFi = 2,
TransportType_Cellular = 3, // In Case the API does not retun the gsm type
TransportType_Other = 4, // I.e USB thethering
TransportType_None = 5 // I.e Airplane Mode or no active network device
};
Q_ENUM(TransportType);
// Returns the current type of Network Connection
virtual TransportType getTransportType() = 0;
signals:
// Fires when the Device Connects to an unsecured Network
void unsecuredNetwork(const QString& networkName, const QString& networkId);
// Fires on when the connected WIFI Changes
// TODO: Only windows-networkwatcher has this, the other plattforms should
// too.
void networkChanged(QString newBSSID);
// Fired when the Device changed the Type of Transport
void transportChanged(NetworkWatcherImpl::TransportType transportType);
private:
bool m_active = false;
};
#endif // NETWORKWATCHERIMPL_H

View file

@ -0,0 +1,189 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "pinghelper.h"
#include <QDateTime>
#include <cmath>
#include "dnspingsender.h"
#include "leakdetector.h"
#include "logger.h"
#include "pingsender.h"
#include "pingsenderfactory.h"
// Any X seconds, a new ping.
constexpr uint32_t PING_TIMEOUT_SEC = 1;
// Maximum window size for ping statistics.
constexpr int PING_STATS_WINDOW = 32;
namespace {
Logger logger("PingHelper");
}
PingHelper::PingHelper() {
MZ_COUNT_CTOR(PingHelper);
m_sequence = 0;
m_pingData.resize(PING_STATS_WINDOW);
connect(&m_pingTimer, &QTimer::timeout, this, &PingHelper::nextPing);
}
PingHelper::~PingHelper() { MZ_COUNT_DTOR(PingHelper); }
void PingHelper::start(const QString& serverIpv4Gateway,
const QString& deviceIpv4Address) {
logger.debug() << "PingHelper activated for server:"
<< logger.sensitive(serverIpv4Gateway);
m_gateway = QHostAddress(serverIpv4Gateway);
m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0));
m_pingSender = PingSenderFactory::create(m_source, this);
// Some platforms require root access to send and receive ICMP pings. If
// we happen to be on one of these unlucky devices, create a DnsPingSender
// instead.
if (!m_pingSender->isValid()) {
delete m_pingSender;
m_pingSender = new DnsPingSender(m_source, this);
}
connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived,
Qt::QueuedConnection);
connect(m_pingSender, &PingSender::criticalPingError, this,
[]() { logger.info() << "Encountered Unrecoverable ping error"; });
// Reset the ping statistics
m_sequence = 0;
for (int i = 0; i < PING_STATS_WINDOW; i++) {
m_pingData[i].timestamp = -1;
m_pingData[i].latency = -1;
m_pingData[i].sequence = 0;
}
m_pingTimer.start(PING_TIMEOUT_SEC * 1000);
}
void PingHelper::stop() {
logger.debug() << "PingHelper deactivated";
if (m_pingSender) {
delete m_pingSender;
m_pingSender = nullptr;
}
m_pingTimer.stop();
}
void PingHelper::nextPing() {
#ifdef MZ_DEBUG
logger.debug() << "Sending ping seq:" << m_sequence;
#endif
// The ICMP sequence number is used to match replies with their originating
// request, and serves as an index into the circular buffer. Overflows of
// the sequence number acceptable.
int index = m_sequence % PING_STATS_WINDOW;
m_pingData[index].timestamp = QDateTime::currentMSecsSinceEpoch();
m_pingData[index].latency = -1;
m_pingData[index].sequence = m_sequence;
m_pingSender->sendPing(m_gateway, m_sequence);
m_sequence++;
}
void PingHelper::pingReceived(quint16 sequence) {
int index = sequence % PING_STATS_WINDOW;
if (m_pingData[index].sequence == sequence) {
qint64 sendTime = m_pingData[index].timestamp;
m_pingData[index].latency = QDateTime::currentMSecsSinceEpoch() - sendTime;
emit pingSentAndReceived(m_pingData[index].latency);
#ifdef MZ_DEBUG
logger.debug() << "Ping answer received seq:" << sequence
<< "avg:" << latency()
<< "loss:" << QString("%1%").arg(loss() * 100.0)
<< "stddev:" << stddev();
#endif
}
}
uint PingHelper::latency() const {
int recvCount = 0;
qint64 totalMsec = 0;
for (const PingSendData& data : m_pingData) {
if (data.latency < 0) {
continue;
}
recvCount++;
totalMsec += data.latency;
}
if (recvCount <= 0) {
return 0.0;
}
// Add half the denominator to produce nearest-integer rounding.
totalMsec += recvCount / 2;
return static_cast<uint>(totalMsec / recvCount);
}
uint PingHelper::stddev() const {
int recvCount = 0;
qint64 totalVariance = 0;
uint average = PingHelper::latency();
for (const PingSendData& data : m_pingData) {
if (data.latency < 0) {
continue;
}
recvCount++;
totalVariance += (average - data.latency) * (average - data.latency);
}
if (recvCount <= 0) {
return 0.0;
}
return std::sqrt((double)totalVariance / recvCount);
}
uint PingHelper::maximum() const {
uint maxRtt = 0;
for (const PingSendData& data : m_pingData) {
if (data.latency < 0) {
continue;
}
if (data.latency > maxRtt &&
data.latency < std::numeric_limits<uint>::max()) {
maxRtt = static_cast<uint>(data.latency);
}
}
return maxRtt;
}
double PingHelper::loss() const {
int sendCount = 0;
int recvCount = 0;
// Don't count pings that are possibly still in flight as losses.
qint64 sendBefore =
QDateTime::currentMSecsSinceEpoch() - (PING_TIMEOUT_SEC * 1000);
for (const PingSendData& data : m_pingData) {
if (data.latency >= 0) {
recvCount++;
sendCount++;
} else if ((data.timestamp > 0) && (data.timestamp < sendBefore)) {
sendCount++;
}
}
if (sendCount <= 0) {
return 0.0;
}
return (double)(sendCount - recvCount) / PING_STATS_WINDOW;
}

View file

@ -0,0 +1,64 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef PINGHELPER_H
#define PINGHELPER_H
#include <QHostAddress>
#include <QList>
#include <QObject>
#include <QTimer>
#include <QVector>
class PingSender;
class PingHelper final : public QObject {
private:
Q_OBJECT
Q_DISABLE_COPY_MOVE(PingHelper)
public:
PingHelper();
~PingHelper();
void start(const QString& serverIpv4Gateway,
const QString& deviceIpv4Address);
void stop();
uint latency() const;
uint stddev() const;
uint maximum() const;
double loss() const;
signals:
void pingSentAndReceived(qint64 msec);
private:
void nextPing();
void pingReceived(quint16 sequence);
private:
QHostAddress m_gateway;
QHostAddress m_source;
quint16 m_sequence = 0;
class PingSendData {
public:
PingSendData() {
timestamp = -1;
latency = -1;
sequence = 0;
}
qint64 timestamp;
qint64 latency;
quint16 sequence;
};
QVector<PingSendData> m_pingData;
QTimer m_pingTimer;
PingSender* m_pingSender = nullptr;
};
#endif // PINGHELPER_H

View file

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "pingsender.h"
#include "logger.h"
namespace {
Logger logger("PingSender");
}
quint16 PingSender::inetChecksum(const void* data, size_t len) {
int nleft, sum;
quint16* w;
union {
quint16 us;
quint8 uc[2];
} last;
quint16 answer;
nleft = static_cast<int>(len);
sum = 0;
w = (quint16*)data;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (nleft > 1) {
sum += *w++;
nleft -= 2;
}
/* mop up an odd byte, if necessary */
if (nleft == 1) {
last.uc[0] = *(quint8*)w;
last.uc[1] = 0;
sum += last.us;
}
/* add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return (answer);
}

View file

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef PINGSENDER_H
#define PINGSENDER_H
#include <QElapsedTimer>
#include <QHostAddress>
#include <QObject>
class PingSender : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(PingSender)
public:
PingSender(QObject* parent = nullptr) : QObject(parent) {}
virtual ~PingSender() = default;
virtual bool isValid() { return true; };
virtual void sendPing(const QHostAddress& destination, quint16 sequence) = 0;
static quint16 inetChecksum(const void* data, size_t length);
signals:
void recvPing(quint16 sequence);
void criticalPingError();
};
#endif // PINGSENDER_H

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/. */
#include "pingsenderfactory.h"
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
//# include "platforms/linux/linuxpingsender.h"
#elif defined(MZ_MACOS) || defined(MZ_IOS)
# include "platforms/macos/macospingsender.h"
#elif defined(MZ_WINDOWS)
// #include "platforms/windows/windowspingsender.h"
#elif defined(MZ_DUMMY) || defined(UNIT_TEST)
# include "platforms/dummy/dummypingsender.h"
#else
# error "Unsupported platform"
#endif
PingSender* PingSenderFactory::create(const QHostAddress& source,
QObject* parent) {
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
return nullptr;
//return new LinuxPingSender(source, parent);
#elif defined(MZ_MACOS) || defined(MZ_IOS)
return new MacOSPingSender(source, parent);
#elif defined(MZ_WINDOWS)
return nullptr;
//return new WindowsPingSender(source, parent);
#else
return new DummyPingSender(source, parent);
#endif
}

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/. */
#ifndef PINGSENDERFACTORY_H
#define PINGSENDERFACTORY_H
class PingSender;
class QHostAddress;
class QObject;
class PingSenderFactory final {
public:
PingSenderFactory() = delete;
static PingSender* create(const QHostAddress& source, QObject* parent);
};
#endif // PINGSENDERFACTORY_H

View file

@ -0,0 +1,286 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ipaddress.h"
#include <QtMath>
#include "leakdetector.h"
IPAddress::IPAddress() { MZ_COUNT_CTOR(IPAddress); }
IPAddress::IPAddress(const QString& ip) {
MZ_COUNT_CTOR(IPAddress);
if (ip.contains("/")) {
QPair<QHostAddress, int> p = QHostAddress::parseSubnet(ip);
m_address = p.first;
m_prefixLength = p.second;
} else {
m_address = QHostAddress(ip);
m_prefixLength = 999999;
}
if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
if (m_prefixLength >= 32) {
m_prefixLength = 32;
}
} else if (m_address.protocol() == QAbstractSocket::IPv6Protocol) {
if (m_prefixLength >= 128) {
m_prefixLength = 128;
}
} else {
Q_ASSERT(false);
}
}
IPAddress::IPAddress(const IPAddress& other) {
MZ_COUNT_CTOR(IPAddress);
*this = other;
}
IPAddress& IPAddress::operator=(const IPAddress& other) {
if (this == &other) return *this;
m_address = other.m_address;
m_prefixLength = other.m_prefixLength;
return *this;
}
IPAddress::IPAddress(const QHostAddress& address) : m_address(address) {
MZ_COUNT_CTOR(IPAddress);
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
m_prefixLength = 32;
} else {
Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
m_prefixLength = 128;
}
}
IPAddress::IPAddress(const QHostAddress& address, int prefixLength)
: m_address(address), m_prefixLength(prefixLength) {
MZ_COUNT_CTOR(IPAddress);
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
Q_ASSERT(prefixLength >= 0 && prefixLength <= 32);
} else {
Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
Q_ASSERT(prefixLength >= 0 && prefixLength <= 128);
}
}
IPAddress::~IPAddress() { MZ_COUNT_DTOR(IPAddress); }
QAbstractSocket::NetworkLayerProtocol IPAddress::type() const {
return m_address.protocol();
}
QHostAddress IPAddress::netmask() const {
if (m_address.protocol() == QAbstractSocket::IPv6Protocol) {
Q_IPV6ADDR rawNetmask = {0};
Q_ASSERT(m_prefixLength <= 128);
memset(&rawNetmask, 0xff, m_prefixLength / 8);
if (m_prefixLength % 8) {
rawNetmask[m_prefixLength / 8] = 0xFF ^ (0xFF >> (m_prefixLength % 8));
}
return QHostAddress(rawNetmask);
} else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
quint32 rawNetmask = 0xffffffff;
Q_ASSERT(m_prefixLength <= 32);
if (m_prefixLength < 32) {
rawNetmask ^= (0xffffffff >> m_prefixLength);
}
return QHostAddress(rawNetmask);
} else {
return QHostAddress();
}
}
QHostAddress IPAddress::hostmask() const {
if (m_address.protocol() == QAbstractSocket::IPv6Protocol) {
Q_IPV6ADDR rawHostmask = {0};
int offset = (m_prefixLength + 7) / 8;
Q_ASSERT(m_prefixLength <= 128);
memset(&rawHostmask[offset], 0xff, sizeof(rawHostmask) - offset);
if (m_prefixLength % 8) {
rawHostmask[m_prefixLength / 8] = 0xFF >> (m_prefixLength % 8);
}
return QHostAddress(rawHostmask);
} else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
if (m_prefixLength < 32) {
return QHostAddress(0xffffffff >> m_prefixLength);
} else {
quint32 zero = 0;
return QHostAddress(zero);
}
} else {
return QHostAddress();
}
}
QHostAddress IPAddress::broadcastAddress() const {
if (m_address.protocol() == QAbstractSocket::IPv6Protocol) {
Q_IPV6ADDR rawAddress = m_address.toIPv6Address();
int offset = (m_prefixLength + 7) / 8;
memset(&rawAddress[offset], 0xff, sizeof(rawAddress) - offset);
if (m_prefixLength % 8) {
rawAddress[m_prefixLength / 8] |= 0xFF >> (m_prefixLength % 8);
}
return QHostAddress(rawAddress);
} else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
quint32 rawAddress = m_address.toIPv4Address();
if (m_prefixLength < 32) {
rawAddress |= (0xffffffff >> m_prefixLength);
}
return QHostAddress(rawAddress);
} else {
return QHostAddress();
}
}
bool IPAddress::overlaps(const IPAddress& other) const {
if (m_prefixLength < other.m_prefixLength) {
return contains(other.m_address);
} else {
return other.contains(m_address);
}
}
bool IPAddress::contains(const QHostAddress& address) const {
if (address.protocol() != m_address.protocol()) {
return false;
}
if (m_prefixLength == 0) {
return true;
}
if (m_address.protocol() == QAbstractSocket::IPv6Protocol) {
Q_IPV6ADDR a = m_address.toIPv6Address();
Q_IPV6ADDR b = address.toIPv6Address();
int bytes = m_prefixLength / 8;
if (bytes > 0) {
if (memcmp(&a, &b, bytes) != 0) {
return false;
}
}
if (m_prefixLength % 8) {
quint8 diff = (a[bytes] ^ b[bytes]) >> (8 - m_prefixLength % 8);
return (diff == 0);
}
return true;
}
if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
quint32 diff = m_address.toIPv4Address() ^ address.toIPv4Address();
if (m_prefixLength < 32) {
diff >>= (32 - m_prefixLength);
}
return (diff == 0);
}
return false;
}
bool IPAddress::operator==(const IPAddress& other) const {
return m_address == other.m_address && m_prefixLength == other.m_prefixLength;
}
bool IPAddress::subnetOf(const IPAddress& other) const {
if (other.m_address.protocol() != m_address.protocol()) {
return false;
}
if (m_prefixLength < other.m_prefixLength) {
return false;
}
return other.contains(m_address);
}
QList<IPAddress> IPAddress::subnets() const {
QList<IPAddress> list;
if (m_address.protocol() == QAbstractSocket::IPv4Protocol) {
if (m_prefixLength >= 32) {
list.append(*this);
return list;
}
quint32 rawAddress = m_address.toIPv4Address();
list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1));
rawAddress |= (0x80000000 >> m_prefixLength);
list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1));
return list;
}
Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol);
if (m_prefixLength >= 128) {
list.append(*this);
return list;
}
Q_IPV6ADDR rawAddress = m_address.toIPv6Address();
list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1));
rawAddress[m_prefixLength / 8] |= (0x80 >> (m_prefixLength % 8));
list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1));
return list;
}
// static
QList<IPAddress> 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)) {
newResults.append(ip);
} else if (exclude.subnetOf(ip) && exclude != ip) {
QList<IPAddress> range = ip.excludeAddresses(exclude);
newResults.append(range);
}
}
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

@ -0,0 +1,53 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IPADDRESS_H
#define IPADDRESS_H
#include <QHostAddress>
class IPAddress final {
public:
static QList<IPAddress> excludeAddresses(const QList<IPAddress>& sourceList,
const QList<IPAddress>& excludeList);
IPAddress();
IPAddress(const QString& ip);
IPAddress(const QHostAddress& address);
IPAddress(const QHostAddress& address, int prefixLength);
IPAddress(const IPAddress& other);
IPAddress& operator=(const IPAddress& other);
~IPAddress();
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; }
QHostAddress netmask() const;
QHostAddress hostmask() const;
QHostAddress broadcastAddress() const;
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:
QHostAddress m_address;
int m_prefixLength;
};
#endif // IPADDRESS_H

View file

@ -0,0 +1,75 @@
/* 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 MZ_DEBUG
static QMutex s_leakDetector;
QHash<QString, QHash<void*, uint32_t>> s_leaks;
#endif
LeakDetector::LeakDetector() {
#ifndef MZ_DEBUG
qFatal("LeakDetector _must_ be created in debug builds only!");
#endif
}
LeakDetector::~LeakDetector() {
#ifdef MZ_DEBUG
QTextStream out(stderr);
out << "== MZ - 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 MZ_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

@ -0,0 +1,41 @@
/* 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 MZ_DEBUG
# define MZ_COUNT_CTOR(_type) \
do { \
static_assert(std::is_class<_type>(), \
"Token '" #_type "' is not a class type."); \
LeakDetector::logCtor((void*)this, #_type, sizeof(*this)); \
} while (0)
# define MZ_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 MZ_COUNT_CTOR(_type)
# define MZ_COUNT_DTOR(_type)
#endif
class LeakDetector {
public:
LeakDetector();
~LeakDetector();
#ifdef MZ_DEBUG
static void logCtor(void* ptr, const char* typeName, uint32_t size);
static void logDtor(void* ptr, const char* typeName, uint32_t size);
#endif
};
#endif // LEAKDETECTOR_H

View file

@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LOGLEVEL_H
#define LOGLEVEL_H
enum LogLevel {
Trace = 0,
Debug,
Info,
Warning,
Error,
};
#endif // LOGLEVEL_H

View file

@ -0,0 +1,76 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "signalhandler.h"
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include "logger.h"
namespace {
Logger logger("SignalHandler");
int s_signalpipe = -1;
} // namespace
SignalHandler::SignalHandler() {
Q_ASSERT(s_signalpipe < 0);
int quitSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
sigset_t mask;
sigemptyset(&mask);
for (auto sig : quitSignals) {
sigaddset(&mask, sig);
}
if (pipe(m_pipefds) != 0) {
logger.error() << "Unable to create signal wakeup pipe";
return;
}
fcntl(m_pipefds[0], F_SETFL, fcntl(m_pipefds[0], F_GETFL) | O_NONBLOCK);
s_signalpipe = m_pipefds[1];
m_notifier = new QSocketNotifier(m_pipefds[0], QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this,
&SignalHandler::pipeReadReady);
struct sigaction sa;
sa.sa_handler = SignalHandler::saHandler;
sa.sa_mask = mask;
sa.sa_flags = 0;
for (auto sig : quitSignals) {
sigaction(sig, &sa, nullptr);
}
}
SignalHandler::~SignalHandler() {
s_signalpipe = -1;
if (m_pipefds[0] >= 0) {
close(m_pipefds[0]);
}
if (m_pipefds[1] >= 1) {
close(m_pipefds[1]);
}
}
void SignalHandler::pipeReadReady() {
int signal;
if (read(m_pipefds[0], &signal, sizeof(signal)) == sizeof(signal)) {
logger.debug() << "Signal" << signal;
emit quitRequested();
}
}
void SignalHandler::saHandler(int signal) {
if (s_signalpipe >= 0) {
if (write(s_signalpipe, &signal, sizeof(signal)) != sizeof(signal)) {
logger.warning() << "Unable to write in the pipe";
}
}
}

View file

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef SIGNALHANDLER_H
#define SIGNALHANDLER_H
#include <QObject>
#include <QSocketNotifier>
class SignalHandler final : public QObject {
Q_OBJECT
public:
SignalHandler();
~SignalHandler();
private slots:
void pipeReadReady();
private:
static void saHandler(int signal);
int m_pipefds[2] = {-1, -1};
QSocketNotifier* m_notifier = nullptr;
signals:
void quitRequested();
};
#endif // SIGNALHANDLER_H