WireGuard for MacOS (#248)
* WireGuard for MacOS * Fix openvpn block-outside-dns
This commit is contained in:
parent
ed5dc7cdfd
commit
35ecb8499d
118 changed files with 5150 additions and 3486 deletions
|
|
@ -3,23 +3,24 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "dnsutilsmacos.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <systemconfiguration/scdynamicstore.h>
|
||||
#include <systemconfiguration/scpreferences.h>
|
||||
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <systemconfiguration/scpreferences.h>
|
||||
#include <systemconfiguration/scdynamicstore.h>
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "DnsUtilsMacos");
|
||||
Logger logger("DnsUtilsMacos");
|
||||
}
|
||||
|
||||
DnsUtilsMacos::DnsUtilsMacos(QObject* parent) : DnsUtils(parent) {
|
||||
MVPN_COUNT_CTOR(DnsUtilsMacos);
|
||||
MZ_COUNT_CTOR(DnsUtilsMacos);
|
||||
|
||||
m_scStore = SCDynamicStoreCreate(kCFAllocatorSystemDefault,
|
||||
CFSTR("mozillavpn"), nullptr, nullptr);
|
||||
CFSTR("amneziavpn"), nullptr, nullptr);
|
||||
if (m_scStore == nullptr) {
|
||||
logger.error() << "Failed to create system configuration store ref";
|
||||
}
|
||||
|
|
@ -28,7 +29,7 @@ DnsUtilsMacos::DnsUtilsMacos(QObject* parent) : DnsUtils(parent) {
|
|||
}
|
||||
|
||||
DnsUtilsMacos::~DnsUtilsMacos() {
|
||||
MVPN_COUNT_DTOR(DnsUtilsMacos);
|
||||
MZ_COUNT_DTOR(DnsUtilsMacos);
|
||||
restoreResolvers();
|
||||
logger.debug() << "DnsUtilsMacos destroyed.";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@
|
|||
#ifndef DNSUTILSMACOS_H
|
||||
#define DNSUTILSMACOS_H
|
||||
|
||||
#include "dnsutils.h"
|
||||
#include <systemconfiguration/scdynamicstore.h>
|
||||
#include <systemconfiguration/systemconfiguration.h>
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include <systemconfiguration/scdynamicstore.h>
|
||||
#include <systemconfiguration/systemconfiguration.h>
|
||||
#include "daemon/dnsutils.h"
|
||||
|
||||
class DnsUtilsMacos final : public DnsUtils {
|
||||
Q_OBJECT
|
||||
|
|
|
|||
|
|
@ -3,13 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "iputilsmacos.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "macosdaemon.h"
|
||||
#include "daemon/wireguardutils.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
|
|
@ -19,33 +12,33 @@
|
|||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "macosdaemon.h"
|
||||
|
||||
constexpr uint32_t ETH_MTU = 1500;
|
||||
constexpr uint32_t WG_MTU_OVERHEAD = 80;
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "IPUtilsMacos");
|
||||
Logger logger("IPUtilsMacos");
|
||||
}
|
||||
|
||||
IPUtilsMacos::IPUtilsMacos(QObject* parent) : IPUtils(parent) {
|
||||
MVPN_COUNT_CTOR(IPUtilsMacos);
|
||||
MZ_COUNT_CTOR(IPUtilsMacos);
|
||||
logger.debug() << "IPUtilsMacos created.";
|
||||
}
|
||||
|
||||
IPUtilsMacos::~IPUtilsMacos() {
|
||||
MVPN_COUNT_DTOR(IPUtilsMacos);
|
||||
MZ_COUNT_DTOR(IPUtilsMacos);
|
||||
logger.debug() << "IPUtilsMacos destroyed.";
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addInterfaceIPs(const InterfaceConfig& config) {
|
||||
if (!addIP4AddressToDevice(config)) {
|
||||
return false;
|
||||
}
|
||||
if (config.m_ipv6Enabled) {
|
||||
if (!addIP6AddressToDevice(config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||
|
|
@ -132,7 +125,7 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
|||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCAIFADDR, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv4: " << deviceAddr
|
||||
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -172,7 +165,7 @@ bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
|||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv6: " << deviceAddr
|
||||
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
#ifndef IPUTILSMACOS_H
|
||||
#define IPUTILSMACOS_H
|
||||
|
||||
#include "daemon/iputils.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "daemon/iputils.h"
|
||||
|
||||
class IPUtilsMacos final : public IPUtils {
|
||||
public:
|
||||
IPUtilsMacos(QObject* parent);
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macosdaemon.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "wgquickprocess.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
|
|
@ -18,13 +15,16 @@
|
|||
#include <QTextStream>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacOSDaemon");
|
||||
Logger logger("MacOSDaemon");
|
||||
MacOSDaemon* s_daemon = nullptr;
|
||||
} // namespace
|
||||
|
||||
MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
|
||||
MVPN_COUNT_CTOR(MacOSDaemon);
|
||||
MZ_COUNT_CTOR(MacOSDaemon);
|
||||
|
||||
logger.debug() << "Daemon created";
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
|
|||
}
|
||||
|
||||
MacOSDaemon::~MacOSDaemon() {
|
||||
MVPN_COUNT_DTOR(MacOSDaemon);
|
||||
MZ_COUNT_DTOR(MacOSDaemon);
|
||||
|
||||
logger.debug() << "Daemon released";
|
||||
|
||||
|
|
@ -50,25 +50,3 @@ MacOSDaemon* MacOSDaemon::instance() {
|
|||
Q_ASSERT(s_daemon);
|
||||
return s_daemon;
|
||||
}
|
||||
|
||||
QByteArray MacOSDaemon::getStatus() {
|
||||
logger.debug() << "Status request";
|
||||
|
||||
bool connected = m_connections.contains(0);
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "status");
|
||||
obj.insert("connected", connected);
|
||||
|
||||
if (connected) {
|
||||
const ConnectionState& state = m_connections.value(0).m_config;
|
||||
WireguardUtils::peerStatus status =
|
||||
m_wgutils->getPeerStatus(state.m_config.m_serverPublicKey);
|
||||
obj.insert("serverIpv4Gateway", state.m_config.m_serverIpv4Gateway);
|
||||
obj.insert("deviceIpv4Address", state.m_config.m_deviceIpv4Address);
|
||||
obj.insert("date", state.m_date.toString());
|
||||
obj.insert("txBytes", QJsonValue(status.txBytes));
|
||||
obj.insert("rxBytes", QJsonValue(status.rxBytes));
|
||||
}
|
||||
|
||||
return QJsonDocument(obj).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
#ifndef MACOSDAEMON_H
|
||||
#define MACOSDAEMON_H
|
||||
|
||||
#include "daemon.h"
|
||||
#include "daemon/daemon.h"
|
||||
#include "dnsutilsmacos.h"
|
||||
#include "iputilsmacos.h"
|
||||
#include "wireguardutilsmacos.h"
|
||||
|
|
@ -19,8 +19,6 @@ class MacOSDaemon final : public Daemon {
|
|||
|
||||
static MacOSDaemon* instance();
|
||||
|
||||
QByteArray getStatus() override;
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
bool supportDnsUtils() const override { return true; }
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macosdaemonserver.h"
|
||||
#include "commandlineparser.h"
|
||||
#include "daemon/daemonlocalserver.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "macosdaemon.h"
|
||||
#include "mozillavpn.h"
|
||||
#include "signalhandler.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacOSDaemonServer");
|
||||
}
|
||||
|
||||
MacOSDaemonServer::MacOSDaemonServer(QObject* parent)
|
||||
: Command(parent, "macosdaemon", "Activate the macos daemon") {
|
||||
MVPN_COUNT_CTOR(MacOSDaemonServer);
|
||||
}
|
||||
|
||||
MacOSDaemonServer::~MacOSDaemonServer() { MVPN_COUNT_DTOR(MacOSDaemonServer); }
|
||||
|
||||
int MacOSDaemonServer::run(QStringList& tokens) {
|
||||
Q_ASSERT(!tokens.isEmpty());
|
||||
QString appName = tokens[0];
|
||||
|
||||
QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv());
|
||||
|
||||
QCoreApplication::setApplicationName("Mozilla VPN Daemon");
|
||||
QCoreApplication::setApplicationVersion(APP_VERSION);
|
||||
|
||||
if (tokens.length() > 1) {
|
||||
QList<CommandLineParser::Option*> options;
|
||||
return CommandLineParser::unknownOption(this, appName, tokens[1], options,
|
||||
false);
|
||||
}
|
||||
|
||||
MacOSDaemon daemon;
|
||||
|
||||
DaemonLocalServer server(qApp);
|
||||
if (!server.initialize()) {
|
||||
logger.error() << "Failed to initialize the server";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Signal handling for a proper shutdown.
|
||||
SignalHandler sh;
|
||||
QObject::connect(&sh, &SignalHandler::quitRequested,
|
||||
[]() { MacOSDaemon::instance()->deactivate(); });
|
||||
QObject::connect(&sh, &SignalHandler::quitRequested, &app,
|
||||
&QCoreApplication::quit, Qt::QueuedConnection);
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
static Command::RegistrationProxy<MacOSDaemonServer> s_commandMacOSDaemon;
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MACOSDAEMONSERVER_H
|
||||
#define MACOSDAEMONSERVER_H
|
||||
|
||||
#include "command.h"
|
||||
|
||||
class MacOSDaemonServer final : public Command {
|
||||
public:
|
||||
explicit MacOSDaemonServer(QObject* parent);
|
||||
~MacOSDaemonServer();
|
||||
|
||||
int run(QStringList& tokens) override;
|
||||
};
|
||||
|
||||
#endif // MACOSDAEMONSERVER_H
|
||||
|
|
@ -3,15 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macosroutemonitor.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QScopeGuard>
|
||||
#include <QTimer>
|
||||
#include <QProcess>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
|
|
@ -24,24 +15,21 @@
|
|||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QProcess>
|
||||
#include <QScopeGuard>
|
||||
#include <QTimer>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacosRouteMonitor");
|
||||
|
||||
template <typename T>
|
||||
static T* sockaddr_cast(QByteArray& data) {
|
||||
const struct sockaddr* sa = (const struct sockaddr*)data.constData();
|
||||
Q_ASSERT(sa->sa_len <= data.length());
|
||||
if (data.length() >= (int)sizeof(T)) {
|
||||
return (T*)data.data();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Logger logger("MacosRouteMonitor");
|
||||
} // namespace
|
||||
|
||||
MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent)
|
||||
: QObject(parent), m_ifname(ifname) {
|
||||
MVPN_COUNT_CTOR(MacosRouteMonitor);
|
||||
MZ_COUNT_CTOR(MacosRouteMonitor);
|
||||
logger.debug() << "MacosRouteMonitor created.";
|
||||
|
||||
m_rtsock = socket(PF_ROUTE, SOCK_RAW, 0);
|
||||
|
|
@ -50,96 +38,250 @@ MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent)
|
|||
return;
|
||||
}
|
||||
|
||||
// Disable replies to our own messages.
|
||||
int off = 0;
|
||||
setsockopt(m_rtsock, SOL_SOCKET, SO_USELOOPBACK, &off, sizeof(off));
|
||||
m_ifindex = if_nametoindex(qPrintable(ifname));
|
||||
|
||||
m_notifier = new QSocketNotifier(m_rtsock, QSocketNotifier::Read, this);
|
||||
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||
&MacosRouteMonitor::rtsockReady);
|
||||
|
||||
// Grab the default routes at startup.
|
||||
rtmFetchRoutes(AF_INET);
|
||||
rtmFetchRoutes(AF_INET6);
|
||||
}
|
||||
|
||||
MacosRouteMonitor::~MacosRouteMonitor() {
|
||||
MVPN_COUNT_DTOR(MacosRouteMonitor);
|
||||
MZ_COUNT_DTOR(MacosRouteMonitor);
|
||||
flushExclusionRoutes();
|
||||
if (m_rtsock >= 0) {
|
||||
close(m_rtsock);
|
||||
}
|
||||
logger.debug() << "MacosRouteMonitor destroyed.";
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::handleRtmAdd(const struct rt_msghdr* rtm,
|
||||
const QByteArray& payload) {
|
||||
QList<QByteArray> addrlist = parseAddrList(payload);
|
||||
|
||||
// Ignore routing changes on the tunnel interfaces
|
||||
if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) {
|
||||
if (m_ifname == addrToString(addrlist[1])) {
|
||||
return;
|
||||
}
|
||||
// Compare memory against zero.
|
||||
static int memcmpzero(const void* data, size_t len) {
|
||||
const quint8* ptr = static_cast<const quint8*>(data);
|
||||
while (len--) {
|
||||
if (*ptr++) return 1;
|
||||
}
|
||||
|
||||
QStringList list;
|
||||
for (auto addr : addrlist) {
|
||||
list.append(addrToString(addr));
|
||||
}
|
||||
logger.debug() << "Route added by" << rtm->rtm_pid
|
||||
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm,
|
||||
const QByteArray& payload) {
|
||||
QList<QByteArray> addrlist = parseAddrList(payload);
|
||||
|
||||
// Ignore routing changes on the tunnel interfaces
|
||||
if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) {
|
||||
if (m_ifname == addrToString(addrlist[1])) {
|
||||
return;
|
||||
}
|
||||
// Ignore routing changes on the tunnel interface.
|
||||
if (rtm->rtm_index == m_ifindex) {
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList list;
|
||||
#ifdef MZ_DEBUG
|
||||
for (auto addr : addrlist) {
|
||||
list.append(addrToString(addr));
|
||||
}
|
||||
logger.debug() << "Route deleted by" << rtm->rtm_pid
|
||||
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
|
||||
#endif
|
||||
char ifname[IF_NAMESIZE] = "null";
|
||||
if (rtm->rtm_index != 0) {
|
||||
if_indextoname(rtm->rtm_index, ifname);
|
||||
}
|
||||
logger.debug() << "Route deleted via" << ifname
|
||||
<< QString("addrs(%1):").arg(rtm->rtm_addrs, 0, 16)
|
||||
<< list.join(" ");
|
||||
|
||||
// We expect all useful routes to contain a destination, netmask and gateway.
|
||||
if (!(rtm->rtm_addrs & RTA_DST) || !(rtm->rtm_addrs & RTA_GATEWAY) ||
|
||||
!(rtm->rtm_addrs & RTA_NETMASK) || (addrlist.count() < 3)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for a default route, which should have a netmask of zero.
|
||||
const struct sockaddr* sa =
|
||||
reinterpret_cast<const struct sockaddr*>(addrlist[2].constData());
|
||||
if (sa->sa_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
Q_ASSERT(sa->sa_len <= sizeof(sin));
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
memcpy(&sin, sa, sa->sa_len);
|
||||
if (memcmpzero(&sin.sin_addr, sizeof(sin.sin_addr)) != 0) {
|
||||
return;
|
||||
}
|
||||
} else if (sa->sa_family == AF_INET6) {
|
||||
struct sockaddr_in6 sin6;
|
||||
Q_ASSERT(sa->sa_len <= sizeof(sin6));
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
memcpy(&sin6, sa, sa->sa_len);
|
||||
if (memcmpzero(&sin6.sin6_addr, sizeof(sin6.sin6_addr)) != 0) {
|
||||
return;
|
||||
}
|
||||
} else if (sa->sa_family != AF_UNSPEC) {
|
||||
// We have sometimes seen the default route reported with AF_UNSPEC.
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the default gateway
|
||||
const struct sockaddr* dst =
|
||||
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
|
||||
QAbstractSocket::NetworkLayerProtocol protocol;
|
||||
unsigned int plen;
|
||||
if (dst->sa_family == AF_INET) {
|
||||
m_defaultGatewayIpv4.clear();
|
||||
m_defaultIfindexIpv4 = 0;
|
||||
protocol = QAbstractSocket::IPv4Protocol;
|
||||
plen = 32;
|
||||
} else if (dst->sa_family == AF_INET6) {
|
||||
m_defaultGatewayIpv6.clear();
|
||||
m_defaultIfindexIpv6 = 0;
|
||||
protocol = QAbstractSocket::IPv6Protocol;
|
||||
plen = 128;
|
||||
}
|
||||
|
||||
logger.debug() << "Lost default route via" << ifname
|
||||
<< logger.sensitive(addrToString(addrlist[1]));
|
||||
for (const QHostAddress& addr : m_exclusionRoutes) {
|
||||
if (addr.protocol() == protocol) {
|
||||
logger.debug() << "Removing exclusion route to"
|
||||
<< logger.sensitive(addr.toString());
|
||||
rtmSendRoute(RTM_DELETE, addr, plen, rtm->rtm_index, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::handleRtmChange(const struct rt_msghdr* rtm,
|
||||
void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
||||
const QByteArray& payload) {
|
||||
QList<QByteArray> addrlist = parseAddrList(payload);
|
||||
int ifindex = rtm->rtm_index;
|
||||
char ifname[IF_NAMESIZE] = "null";
|
||||
|
||||
// Ignore routing changes on the tunnel interfaces
|
||||
if ((rtm->rtm_addrs & RTA_DST) && (rtm->rtm_addrs & RTA_GATEWAY)) {
|
||||
if (m_ifname == addrToString(addrlist[1])) {
|
||||
// We expect all useful routes to contain a destination, netmask and gateway.
|
||||
if (!(rtm->rtm_addrs & RTA_DST) || !(rtm->rtm_addrs & RTA_GATEWAY) ||
|
||||
!(rtm->rtm_addrs & RTA_NETMASK) || (addrlist.count() < 3)) {
|
||||
return;
|
||||
}
|
||||
// Ignore route changes that we caused, or routes on the tunnel interface.
|
||||
if (rtm->rtm_index == m_ifindex) {
|
||||
return;
|
||||
}
|
||||
if ((rtm->rtm_pid == getpid()) && (rtm->rtm_type != RTM_GET)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case: If RTA_IFP is set, then we should get the interface index
|
||||
// from the address list instead of rtm_index.
|
||||
if (rtm->rtm_addrs & RTA_IFP) {
|
||||
int addridx = 0;
|
||||
for (int mask = 1; mask < RTA_IFP; mask <<= 1) {
|
||||
if (rtm->rtm_addrs & mask) {
|
||||
addridx++;
|
||||
}
|
||||
}
|
||||
if (addridx >= addrlist.count()) {
|
||||
return;
|
||||
}
|
||||
const char* sdl_data = addrlist[addridx].constData();
|
||||
const struct sockaddr_dl* sdl =
|
||||
reinterpret_cast<const struct sockaddr_dl*>(sdl_data);
|
||||
if (sdl->sdl_family == AF_LINK) {
|
||||
ifindex = sdl->sdl_index;
|
||||
}
|
||||
}
|
||||
|
||||
// Log relevant updates to the routing table.
|
||||
QStringList list;
|
||||
#ifdef MZ_DEBUG
|
||||
for (auto addr : addrlist) {
|
||||
list.append(addrToString(addr));
|
||||
}
|
||||
logger.debug() << "Route changed by" << rtm->rtm_pid
|
||||
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
|
||||
#endif
|
||||
if_indextoname(ifindex, ifname);
|
||||
logger.debug() << "Route update via" << ifname
|
||||
<< QString("addrs(%1):").arg(rtm->rtm_addrs, 0, 16)
|
||||
<< list.join(" ");
|
||||
|
||||
// Check for a default route, which should have a netmask of zero.
|
||||
const struct sockaddr* sa =
|
||||
reinterpret_cast<const struct sockaddr*>(addrlist[2].constData());
|
||||
if (sa->sa_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
Q_ASSERT(sa->sa_len <= sizeof(sin));
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
memcpy(&sin, sa, sa->sa_len);
|
||||
if (memcmpzero(&sin.sin_addr, sizeof(sin.sin_addr)) != 0) {
|
||||
return;
|
||||
}
|
||||
} else if (sa->sa_family == AF_INET6) {
|
||||
struct sockaddr_in6 sin6;
|
||||
Q_ASSERT(sa->sa_len <= sizeof(sin6));
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
memcpy(&sin6, sa, sa->sa_len);
|
||||
if (memcmpzero(&sin6.sin6_addr, sizeof(sin6.sin6_addr)) != 0) {
|
||||
return;
|
||||
}
|
||||
} else if (sa->sa_family != AF_UNSPEC) {
|
||||
// The default route sometimes sets a netmask of AF_UNSPEC.
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine if this is the IPv4 or IPv6 default route.
|
||||
const struct sockaddr* dst =
|
||||
reinterpret_cast<const struct sockaddr*>(addrlist[0].constData());
|
||||
QAbstractSocket::NetworkLayerProtocol protocol;
|
||||
unsigned int plen;
|
||||
int rtm_type = RTM_ADD;
|
||||
if (dst->sa_family == AF_INET) {
|
||||
if (m_defaultIfindexIpv4 != 0) {
|
||||
rtm_type = RTM_CHANGE;
|
||||
}
|
||||
m_defaultGatewayIpv4 = addrlist[1];
|
||||
m_defaultIfindexIpv4 = ifindex;
|
||||
protocol = QAbstractSocket::IPv4Protocol;
|
||||
plen = 32;
|
||||
} else if (dst->sa_family == AF_INET6) {
|
||||
if (m_defaultIfindexIpv6 != 0) {
|
||||
rtm_type = RTM_CHANGE;
|
||||
}
|
||||
m_defaultGatewayIpv6 = addrlist[1];
|
||||
m_defaultIfindexIpv6 = ifindex;
|
||||
protocol = QAbstractSocket::IPv6Protocol;
|
||||
plen = 128;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the exclusion routes with the new default route.
|
||||
logger.debug() << "Updating default route via" << ifname
|
||||
<< addrToString(addrlist[1]);
|
||||
for (const QHostAddress& addr : m_exclusionRoutes) {
|
||||
if (addr.protocol() == protocol) {
|
||||
logger.debug() << "Updating exclusion route to"
|
||||
<< logger.sensitive(addr.toString());
|
||||
rtmSendRoute(rtm_type, addr, plen, ifindex, addrlist[1].constData());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::handleIfaceInfo(const struct if_msghdr* ifm,
|
||||
const QByteArray& payload) {
|
||||
QList<QByteArray> addrlist = parseAddrList(payload);
|
||||
QStringList list;
|
||||
|
||||
if (ifm->ifm_index != if_nametoindex(qPrintable(m_ifname))) {
|
||||
return;
|
||||
}
|
||||
m_ifflags = ifm->ifm_flags;
|
||||
|
||||
QStringList list;
|
||||
#ifdef MZ_DEBUG
|
||||
QList<QByteArray> addrlist = parseAddrList(payload);
|
||||
for (auto addr : addrlist) {
|
||||
list.append(addrToString(addr));
|
||||
}
|
||||
logger.debug() << "Interface " << ifm->ifm_index
|
||||
<< "changed flags:" << ifm->ifm_flags
|
||||
<< QString("addrs(%1):").arg(ifm->ifm_addrs) << list.join(" ");
|
||||
#else
|
||||
Q_UNUSED(payload);
|
||||
#endif
|
||||
logger.debug() << "Interface" << ifm->ifm_index
|
||||
<< "chagned flags:" << ifm->ifm_flags
|
||||
<< QString("addrs(%1):").arg(ifm->ifm_addrs, 0, 16)
|
||||
<< list.join(" ");
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::rtsockReady() {
|
||||
|
|
@ -154,11 +296,13 @@ void MacosRouteMonitor::rtsockReady() {
|
|||
(struct rt_msghdr*)((char*)(_rtm_) + (_rtm_)->rtm_msglen)
|
||||
#endif
|
||||
|
||||
struct rt_msghdr* rtm = (struct rt_msghdr*)buf;
|
||||
struct rt_msghdr* end = (struct rt_msghdr*)(&buf[len]);
|
||||
struct rt_msghdr* rtm = reinterpret_cast<struct rt_msghdr*>(buf);
|
||||
struct rt_msghdr* end = reinterpret_cast<struct rt_msghdr*>(&buf[len]);
|
||||
while (rtm < end) {
|
||||
// Ensure the message fits within the buffer
|
||||
if (RTMSG_NEXT(rtm) > end) {
|
||||
logger.debug() << "Routing message overflowed with length"
|
||||
<< rtm->rtm_msglen;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +311,7 @@ void MacosRouteMonitor::rtsockReady() {
|
|||
switch (rtm->rtm_type) {
|
||||
case RTM_ADD:
|
||||
message.remove(0, sizeof(struct rt_msghdr));
|
||||
handleRtmAdd(rtm, message);
|
||||
handleRtmUpdate(rtm, message);
|
||||
break;
|
||||
case RTM_DELETE:
|
||||
message.remove(0, sizeof(struct rt_msghdr));
|
||||
|
|
@ -175,14 +319,17 @@ void MacosRouteMonitor::rtsockReady() {
|
|||
break;
|
||||
case RTM_CHANGE:
|
||||
message.remove(0, sizeof(struct rt_msghdr));
|
||||
handleRtmChange(rtm, message);
|
||||
handleRtmUpdate(rtm, message);
|
||||
break;
|
||||
case RTM_GET:
|
||||
message.remove(0, sizeof(struct rt_msghdr));
|
||||
handleRtmUpdate(rtm, message);
|
||||
break;
|
||||
case RTM_IFINFO:
|
||||
message.remove(0, sizeof(struct if_msghdr));
|
||||
handleIfaceInfo((struct if_msghdr*)rtm, message);
|
||||
break;
|
||||
default:
|
||||
logger.debug() << "Unknown routing message:" << rtm->rtm_type;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +339,7 @@ void MacosRouteMonitor::rtsockReady() {
|
|||
|
||||
void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
|
||||
int rtaddr, const void* sa) {
|
||||
size_t sa_len = ((struct sockaddr*)sa)->sa_len;
|
||||
size_t sa_len = static_cast<const struct sockaddr*>(sa)->sa_len;
|
||||
Q_ASSERT((rtm->rtm_addrs & rtaddr) == 0);
|
||||
if ((rtm->rtm_msglen + sa_len) > maxlen) {
|
||||
return;
|
||||
|
|
@ -206,18 +353,19 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
|
|||
}
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
|
||||
bool MacosRouteMonitor::rtmSendRoute(int action, const QHostAddress& prefix,
|
||||
unsigned int plen, unsigned int ifindex,
|
||||
const void* gateway) {
|
||||
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
|
||||
sizeof(struct sockaddr_in6) * 2 +
|
||||
sizeof(struct sockaddr_dl);
|
||||
char buf[rtm_max_size];
|
||||
struct rt_msghdr* rtm = (struct rt_msghdr*)buf;
|
||||
sizeof(struct sockaddr_storage);
|
||||
char buf[rtm_max_size] = {0};
|
||||
struct rt_msghdr* rtm = reinterpret_cast<struct rt_msghdr*>(buf);
|
||||
|
||||
memset(rtm, 0, rtm_max_size);
|
||||
rtm->rtm_msglen = sizeof(struct rt_msghdr);
|
||||
rtm->rtm_version = RTM_VERSION;
|
||||
rtm->rtm_type = action;
|
||||
rtm->rtm_index = if_nametoindex(qPrintable(m_ifname));
|
||||
rtm->rtm_index = ifindex;
|
||||
rtm->rtm_flags = RTF_STATIC | RTF_UP;
|
||||
rtm->rtm_addrs = 0;
|
||||
rtm->rtm_pid = 0;
|
||||
|
|
@ -227,9 +375,9 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
|
|||
memset(&rtm->rtm_rmx, 0, sizeof(rtm->rtm_rmx));
|
||||
|
||||
// Append RTA_DST
|
||||
if (prefix.type() == IPAddressRange::IPv6) {
|
||||
if (prefix.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
struct sockaddr_in6 sin6;
|
||||
Q_IPV6ADDR dst = QHostAddress(prefix.ipAddress()).toIPv6Address();
|
||||
Q_IPV6ADDR dst = prefix.toIPv6Address();
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_len = sizeof(sin6);
|
||||
|
|
@ -237,7 +385,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
|
|||
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6);
|
||||
} else {
|
||||
struct sockaddr_in sin;
|
||||
quint32 dst = QHostAddress(prefix.ipAddress()).toIPv4Address();
|
||||
quint32 dst = prefix.toIPv4Address();
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_len = sizeof(sin);
|
||||
|
|
@ -246,23 +394,16 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
|
|||
}
|
||||
|
||||
// Append RTA_GATEWAY
|
||||
if (action != RTM_DELETE) {
|
||||
struct sockaddr_dl sdl;
|
||||
memset(&sdl, 0, sizeof(sdl));
|
||||
sdl.sdl_family = AF_LINK;
|
||||
sdl.sdl_len = offsetof(struct sockaddr_dl, sdl_data) + m_ifname.length();
|
||||
sdl.sdl_index = rtm->rtm_index;
|
||||
sdl.sdl_type = IFT_OTHER;
|
||||
sdl.sdl_nlen = m_ifname.length();
|
||||
sdl.sdl_alen = 0;
|
||||
sdl.sdl_slen = 0;
|
||||
memcpy(&sdl.sdl_data, qPrintable(m_ifname), sdl.sdl_nlen);
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_GATEWAY, &sdl);
|
||||
if (gateway != nullptr) {
|
||||
int family = static_cast<const struct sockaddr*>(gateway)->sa_family;
|
||||
if ((family == AF_INET) || (family == AF_INET6)) {
|
||||
rtm->rtm_flags |= RTF_GATEWAY;
|
||||
}
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_GATEWAY, gateway);
|
||||
}
|
||||
|
||||
// Append RTA_NETMASK
|
||||
int plen = prefix.range();
|
||||
if (prefix.type() == IPAddressRange::IPv6) {
|
||||
if (prefix.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
struct sockaddr_in6 sin6;
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
sin6.sin6_family = AF_INET6;
|
||||
|
|
@ -272,7 +413,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
|
|||
sin6.sin6_addr.s6_addr[plen / 8] = 0xFF ^ (0xFF >> (plen % 8));
|
||||
}
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin6);
|
||||
} else if (prefix.type() == IPAddressRange::IPv4) {
|
||||
} else if (prefix.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
|
|
@ -299,12 +440,122 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::insertRoute(const IPAddressRange& prefix) {
|
||||
return rtmSendRoute(RTM_ADD, prefix);
|
||||
bool MacosRouteMonitor::rtmFetchRoutes(int family) {
|
||||
constexpr size_t rtm_max_size =
|
||||
sizeof(struct rt_msghdr) + sizeof(struct sockaddr_storage) * 2;
|
||||
char buf[rtm_max_size] = {0};
|
||||
struct rt_msghdr* rtm = reinterpret_cast<struct rt_msghdr*>(buf);
|
||||
|
||||
rtm->rtm_msglen = sizeof(struct rt_msghdr);
|
||||
rtm->rtm_version = RTM_VERSION;
|
||||
rtm->rtm_type = RTM_GET;
|
||||
rtm->rtm_flags = RTF_UP | RTF_GATEWAY;
|
||||
rtm->rtm_addrs = 0;
|
||||
rtm->rtm_pid = 0;
|
||||
rtm->rtm_seq = m_rtseq++;
|
||||
rtm->rtm_errno = 0;
|
||||
rtm->rtm_inits = 0;
|
||||
memset(&rtm->rtm_rmx, 0, sizeof(rtm->rtm_rmx));
|
||||
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(struct sockaddr_in));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_len = sizeof(struct sockaddr_in);
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin);
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin);
|
||||
} else if (family == AF_INET6) {
|
||||
struct sockaddr_in6 sin6;
|
||||
memset(&sin6, 0, sizeof(struct sockaddr_in6));
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_len = sizeof(struct sockaddr_in6);
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6);
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin6);
|
||||
} else {
|
||||
logger.warning() << "Unsupported address family";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send the routing message into the kernel.
|
||||
int len = write(m_rtsock, rtm, rtm->rtm_msglen);
|
||||
if (len == rtm->rtm_msglen) {
|
||||
return true;
|
||||
}
|
||||
logger.warning() << "Failed to request routing table:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::deleteRoute(const IPAddressRange& prefix) {
|
||||
return rtmSendRoute(RTM_DELETE, prefix);
|
||||
bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
|
||||
struct sockaddr_dl datalink;
|
||||
memset(&datalink, 0, sizeof(datalink));
|
||||
datalink.sdl_family = AF_LINK;
|
||||
datalink.sdl_len = offsetof(struct sockaddr_dl, sdl_data) + m_ifname.length();
|
||||
datalink.sdl_index = m_ifindex;
|
||||
datalink.sdl_type = IFT_OTHER;
|
||||
datalink.sdl_nlen = m_ifname.length();
|
||||
datalink.sdl_alen = 0;
|
||||
datalink.sdl_slen = 0;
|
||||
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
|
||||
|
||||
return rtmSendRoute(RTM_ADD, prefix.address(), prefix.prefixLength(),
|
||||
m_ifindex, &datalink);
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
|
||||
return rtmSendRoute(RTM_DELETE, prefix.address(), prefix.prefixLength(),
|
||||
m_ifindex, nullptr);
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::addExclusionRoute(const QHostAddress& address) {
|
||||
logger.debug() << "Adding exclusion route for"
|
||||
<< logger.sensitive(address.toString());
|
||||
|
||||
if (m_exclusionRoutes.contains(address)) {
|
||||
logger.warning() << "Exclusion route already exists";
|
||||
return false;
|
||||
}
|
||||
m_exclusionRoutes.append(address);
|
||||
|
||||
// If the default route is known, then updte the routing table immediately.
|
||||
if ((address.protocol() == QAbstractSocket::IPv4Protocol) &&
|
||||
(m_defaultIfindexIpv4 != 0) && !m_defaultGatewayIpv4.isEmpty()) {
|
||||
return rtmSendRoute(RTM_ADD, address, 32, m_defaultIfindexIpv4,
|
||||
m_defaultGatewayIpv4.constData());
|
||||
}
|
||||
if ((address.protocol() == QAbstractSocket::IPv6Protocol) &&
|
||||
(m_defaultIfindexIpv6 != 0) && !m_defaultGatewayIpv6.isEmpty()) {
|
||||
return rtmSendRoute(RTM_ADD, address, 128, m_defaultIfindexIpv6,
|
||||
m_defaultGatewayIpv6.constData());
|
||||
}
|
||||
|
||||
// Otherwise, the default route isn't known yet. Do nothing.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::deleteExclusionRoute(const QHostAddress& address) {
|
||||
logger.debug() << "Deleting exclusion route for"
|
||||
<< logger.sensitive(address.toString());
|
||||
|
||||
m_exclusionRoutes.removeAll(address);
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
return rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr);
|
||||
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
return rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6,
|
||||
nullptr);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::flushExclusionRoutes() {
|
||||
while (!m_exclusionRoutes.isEmpty()) {
|
||||
QHostAddress address = m_exclusionRoutes.takeFirst();
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
rtmSendRoute(RTM_DELETE, address, 32, m_defaultIfindexIpv4, nullptr);
|
||||
} else if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
rtmSendRoute(RTM_DELETE, address, 128, m_defaultIfindexIpv6, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
@ -341,7 +592,10 @@ QString MacosRouteMonitor::addrToString(const struct sockaddr* sa) {
|
|||
}
|
||||
if (sa->sa_family == AF_LINK) {
|
||||
const struct sockaddr_dl* sdl = (const struct sockaddr_dl*)sa;
|
||||
return QString(link_ntoa(sdl));
|
||||
return QString("link#%1:").arg(sdl->sdl_index) + QString(link_ntoa(sdl));
|
||||
}
|
||||
if (sa->sa_family == AF_UNSPEC) {
|
||||
return QString("unspec");
|
||||
}
|
||||
return QString("unknown(af=%1)").arg(sa->sa_family);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@
|
|||
#ifndef MACOSROUTEMONITOR_H
|
||||
#define MACOSROUTEMONITOR_H
|
||||
|
||||
#include "ipaddressrange.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include "ipaddress.h"
|
||||
|
||||
struct if_msghdr;
|
||||
struct rt_msghdr;
|
||||
struct sockaddr;
|
||||
|
|
@ -23,16 +24,21 @@ class MacosRouteMonitor final : public QObject {
|
|||
MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr);
|
||||
~MacosRouteMonitor();
|
||||
|
||||
bool insertRoute(const IPAddressRange& prefix);
|
||||
bool deleteRoute(const IPAddressRange& prefix);
|
||||
bool insertRoute(const IPAddress& prefix);
|
||||
bool deleteRoute(const IPAddress& prefix);
|
||||
int interfaceFlags() { return m_ifflags; }
|
||||
|
||||
bool addExclusionRoute(const QHostAddress& address);
|
||||
bool deleteExclusionRoute(const QHostAddress& address);
|
||||
void flushExclusionRoutes();
|
||||
|
||||
private:
|
||||
void handleRtmAdd(const struct rt_msghdr* msg, const QByteArray& payload);
|
||||
void handleRtmDelete(const struct rt_msghdr* msg, const QByteArray& payload);
|
||||
void handleRtmChange(const struct rt_msghdr* msg, const QByteArray& payload);
|
||||
void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload);
|
||||
void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
|
||||
bool rtmSendRoute(int action, const IPAddressRange& prefix);
|
||||
bool rtmSendRoute(int action, const QHostAddress& prefix, unsigned int plen,
|
||||
unsigned int ifindex, const void* gateway);
|
||||
bool rtmFetchRoutes(int family);
|
||||
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
|
||||
const void* sa);
|
||||
static QList<QByteArray> parseAddrList(const QByteArray& data);
|
||||
|
|
@ -44,7 +50,14 @@ class MacosRouteMonitor final : public QObject {
|
|||
static QString addrToString(const struct sockaddr* sa);
|
||||
static QString addrToString(const QByteArray& data);
|
||||
|
||||
QList<QHostAddress> m_exclusionRoutes;
|
||||
QByteArray m_defaultGatewayIpv4;
|
||||
QByteArray m_defaultGatewayIpv6;
|
||||
unsigned int m_defaultIfindexIpv4 = 0;
|
||||
unsigned int m_defaultIfindexIpv6 = 0;
|
||||
|
||||
QString m_ifname;
|
||||
unsigned int m_ifindex = 0;
|
||||
int m_ifflags = 0;
|
||||
int m_rtsock = -1;
|
||||
int m_rtseq = 0;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "wireguardutilsmacos.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
|
|
@ -12,19 +12,20 @@
|
|||
#include <QLocalSocket>
|
||||
#include <QTimer>
|
||||
|
||||
#include <errno.h>
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "WireguardUtilsMacos");
|
||||
Logger logwireguard(LOG_MACOS, "WireguardGo");
|
||||
Logger logger("WireguardUtilsMacos");
|
||||
Logger logwireguard("WireguardGo");
|
||||
}; // namespace
|
||||
|
||||
WireguardUtilsMacos::WireguardUtilsMacos(QObject* parent)
|
||||
: WireguardUtils(parent), m_tunnel(this) {
|
||||
MVPN_COUNT_CTOR(WireguardUtilsMacos);
|
||||
MZ_COUNT_CTOR(WireguardUtilsMacos);
|
||||
logger.debug() << "WireguardUtilsMacos created.";
|
||||
|
||||
connect(&m_tunnel, SIGNAL(readyReadStandardOutput()), this,
|
||||
|
|
@ -34,7 +35,7 @@ WireguardUtilsMacos::WireguardUtilsMacos(QObject* parent)
|
|||
}
|
||||
|
||||
WireguardUtilsMacos::~WireguardUtilsMacos() {
|
||||
MVPN_COUNT_DTOR(WireguardUtilsMacos);
|
||||
MZ_COUNT_DTOR(WireguardUtilsMacos);
|
||||
logger.debug() << "WireguardUtilsMacos destroyed.";
|
||||
}
|
||||
|
||||
|
|
@ -68,15 +69,12 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name");
|
||||
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
||||
#ifdef QT_DEBUG
|
||||
#ifdef MZ_DEBUG
|
||||
pe.insert("LOG_LEVEL", "debug");
|
||||
#endif
|
||||
m_tunnel.setProcessEnvironment(pe);
|
||||
|
||||
QDir appPath(QCoreApplication::applicationDirPath());
|
||||
appPath.cdUp();
|
||||
appPath.cd("Resources");
|
||||
appPath.cd("utils");
|
||||
QStringList wgArgs = {"-f", "utun"};
|
||||
m_tunnel.start(appPath.filePath("wireguard-go"), wgArgs);
|
||||
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
|
||||
|
|
@ -99,6 +97,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||
// Send a UAPI command to configure the interface
|
||||
QString message("set=1\n");
|
||||
QByteArray privateKey = QByteArray::fromBase64(config.m_privateKey.toUtf8());
|
||||
|
||||
QTextStream out(&message);
|
||||
out << "private_key=" << QString(privateKey.toHex()) << "\n";
|
||||
out << "replace_peers=true\n";
|
||||
|
|
@ -134,14 +133,15 @@ bool WireguardUtilsMacos::deleteInterface() {
|
|||
|
||||
// dummy implementations for now
|
||||
bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||
QByteArray publicKey =
|
||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||
QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||
QByteArray pskKey = QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
||||
|
||||
// Update/create the peer config
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
if (!config.m_serverIpv4AddrIn.isNull()) {
|
||||
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
|
||||
} else if (!config.m_serverIpv6AddrIn.isNull()) {
|
||||
|
|
@ -153,10 +153,12 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
|||
out << config.m_serverPort << "\n";
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) {
|
||||
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
logger.debug() << message;
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
|
|
@ -164,8 +166,9 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
|||
return (err == 0);
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deletePeer(const QString& pubkey) {
|
||||
QByteArray publicKey = QByteArray::fromBase64(qPrintable(pubkey));
|
||||
bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) {
|
||||
QByteArray publicKey =
|
||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
|
|
@ -180,13 +183,10 @@ bool WireguardUtilsMacos::deletePeer(const QString& pubkey) {
|
|||
return (err == 0);
|
||||
}
|
||||
|
||||
WireguardUtils::peerStatus WireguardUtilsMacos::getPeerStatus(
|
||||
const QString& pubkey) {
|
||||
peerStatus status = {0, 0};
|
||||
QString hexkey = QByteArray::fromBase64(pubkey.toUtf8()).toHex();
|
||||
QList<WireguardUtils::PeerStatus> WireguardUtilsMacos::getPeerStatus() {
|
||||
QString reply = uapiCommand("get=1");
|
||||
bool match = false;
|
||||
|
||||
PeerStatus status;
|
||||
QList<PeerStatus> peerList;
|
||||
for (const QString& line : reply.split('\n')) {
|
||||
int eq = line.indexOf('=');
|
||||
if (eq <= 0) {
|
||||
|
|
@ -196,39 +196,90 @@ WireguardUtils::peerStatus WireguardUtilsMacos::getPeerStatus(
|
|||
QString value = line.mid(eq + 1);
|
||||
|
||||
if (name == "public_key") {
|
||||
match = (value == hexkey);
|
||||
continue;
|
||||
} else if (!match) {
|
||||
continue;
|
||||
if (!status.m_pubkey.isEmpty()) {
|
||||
peerList.append(status);
|
||||
}
|
||||
QByteArray pubkey = QByteArray::fromHex(value.toUtf8());
|
||||
status = PeerStatus(pubkey.toBase64());
|
||||
}
|
||||
|
||||
if (name == "tx_bytes") {
|
||||
status.txBytes = value.toDouble();
|
||||
status.m_txBytes = value.toDouble();
|
||||
}
|
||||
if (name == "rx_bytes") {
|
||||
status.rxBytes = value.toDouble();
|
||||
status.m_rxBytes = value.toDouble();
|
||||
}
|
||||
if (name == "last_handshake_time_sec") {
|
||||
status.m_handshake += value.toLongLong() * 1000;
|
||||
}
|
||||
if (name == "last_handshake_time_nsec") {
|
||||
status.m_handshake += value.toLongLong() / 1000000;
|
||||
}
|
||||
}
|
||||
if (!status.m_pubkey.isEmpty()) {
|
||||
peerList.append(status);
|
||||
}
|
||||
|
||||
return status;
|
||||
return peerList;
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddressRange& prefix,
|
||||
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddress& prefix,
|
||||
int hopindex) {
|
||||
Q_UNUSED(hopindex);
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
return m_rtmonitor->insertRoute(prefix);
|
||||
if (prefix.prefixLength() > 0) {
|
||||
return m_rtmonitor->insertRoute(prefix);
|
||||
}
|
||||
|
||||
// Ensure that we do not replace the default route.
|
||||
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
|
||||
return m_rtmonitor->insertRoute(IPAddress("0.0.0.0/1")) &&
|
||||
m_rtmonitor->insertRoute(IPAddress("128.0.0.0/1"));
|
||||
}
|
||||
if (prefix.type() == QAbstractSocket::IPv6Protocol) {
|
||||
return m_rtmonitor->insertRoute(IPAddress("::/1")) &&
|
||||
m_rtmonitor->insertRoute(IPAddress("8000::/1"));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddressRange& prefix,
|
||||
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix,
|
||||
int hopindex) {
|
||||
Q_UNUSED(hopindex);
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
return m_rtmonitor->deleteRoute(prefix);
|
||||
if (prefix.prefixLength() > 0) {
|
||||
return m_rtmonitor->insertRoute(prefix);
|
||||
}
|
||||
|
||||
// Ensure that we do not replace the default route.
|
||||
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
|
||||
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
|
||||
m_rtmonitor->deleteRoute(IPAddress("128.0.0.0/1"));
|
||||
} else if (prefix.type() == QAbstractSocket::IPv6Protocol) {
|
||||
return m_rtmonitor->deleteRoute(IPAddress("::/1")) &&
|
||||
m_rtmonitor->deleteRoute(IPAddress("8000::/1"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::addExclusionRoute(const QHostAddress& address) {
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
return m_rtmonitor->addExclusionRoute(address);
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deleteExclusionRoute(const QHostAddress& address) {
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
return m_rtmonitor->deleteExclusionRoute(address);
|
||||
}
|
||||
|
||||
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
#ifndef WIREGUARDUTILSMACOS_H
|
||||
#define WIREGUARDUTILSMACOS_H
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "macosroutemonitor.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "macosroutemonitor.h"
|
||||
|
||||
class WireguardUtilsMacos final : public WireguardUtils {
|
||||
Q_OBJECT
|
||||
|
||||
|
|
@ -26,11 +26,14 @@ class WireguardUtilsMacos final : public WireguardUtils {
|
|||
bool deleteInterface() override;
|
||||
|
||||
bool updatePeer(const InterfaceConfig& config) override;
|
||||
bool deletePeer(const QString& pubkey) override;
|
||||
peerStatus getPeerStatus(const QString& pubkey) override;
|
||||
bool deletePeer(const InterfaceConfig& config) override;
|
||||
QList<PeerStatus> getPeerStatus() override;
|
||||
|
||||
bool updateRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
|
||||
bool deleteRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
|
||||
bool updateRoutePrefix(const IPAddress& prefix, int hopindex) override;
|
||||
bool deleteRoutePrefix(const IPAddress& prefix, int hopindex) override;
|
||||
|
||||
bool addExclusionRoute(const QHostAddress& address) override;
|
||||
bool deleteExclusionRoute(const QHostAddress& address) override;
|
||||
|
||||
signals:
|
||||
void backendFailure();
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "cryptosettings.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QRandomGenerator>
|
||||
|
||||
constexpr const NSString* SERVICE = @"Mozilla VPN";
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger({LOG_MACOS, LOG_MAIN}, "MacOSCryptoSettings");
|
||||
|
||||
bool initialized = false;
|
||||
QByteArray key;
|
||||
|
||||
} // anonymous
|
||||
|
||||
// static
|
||||
void CryptoSettings::resetKey() {
|
||||
logger.debug() << "Reset the key in the keychain";
|
||||
|
||||
NSData* service = [SERVICE dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
|
||||
|
||||
NSMutableDictionary* query = [[NSMutableDictionary alloc] init];
|
||||
|
||||
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
|
||||
[query setObject:service forKey:(id)kSecAttrGeneric];
|
||||
[query setObject:service forKey:(id)kSecAttrAccount];
|
||||
[query setObject:appId forKey:(id)kSecAttrService];
|
||||
|
||||
SecItemDelete((CFDictionaryRef)query);
|
||||
|
||||
[query release];
|
||||
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool CryptoSettings::getKey(uint8_t output[CRYPTO_SETTINGS_KEY_SIZE]) {
|
||||
#if defined(MVPN_IOS) || defined(MVPN_MACOS_NETWORKEXTENSION) || defined(MVPN_MACOS_DAEMON)
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
|
||||
logger.debug() << "Retrieving the key from the keychain";
|
||||
|
||||
NSData* service = [SERVICE dataUsingEncoding:NSUTF8StringEncoding];
|
||||
|
||||
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
|
||||
NSMutableDictionary* query = [[NSMutableDictionary alloc] init];
|
||||
|
||||
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
|
||||
[query setObject:service forKey:(id)kSecAttrGeneric];
|
||||
[query setObject:service forKey:(id)kSecAttrAccount];
|
||||
[query setObject:appId forKey:(id)kSecAttrService];
|
||||
|
||||
[query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
|
||||
[query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
|
||||
|
||||
NSData* keyData = NULL;
|
||||
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)(void*)&keyData);
|
||||
[query release];
|
||||
|
||||
if (status == noErr) {
|
||||
key = QByteArray::fromNSData(keyData);
|
||||
|
||||
logger.debug() << "Key found with length:" << key.length();
|
||||
if (key.length() == CRYPTO_SETTINGS_KEY_SIZE) {
|
||||
memcpy(output, key.data(), CRYPTO_SETTINGS_KEY_SIZE);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
logger.warning() << "Key not found. Let's create it. Error:" << status;
|
||||
key = QByteArray(CRYPTO_SETTINGS_KEY_SIZE, 0x00);
|
||||
QRandomGenerator* rg = QRandomGenerator::system();
|
||||
for (int i = 0; i < CRYPTO_SETTINGS_KEY_SIZE; ++i) {
|
||||
key[i] = rg->generate() & 0xFF;
|
||||
}
|
||||
|
||||
query = [[NSMutableDictionary alloc] init];
|
||||
|
||||
[query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
|
||||
[query setObject:service forKey:(id)kSecAttrGeneric];
|
||||
[query setObject:service forKey:(id)kSecAttrAccount];
|
||||
[query setObject:appId forKey:(id)kSecAttrService];
|
||||
|
||||
SecItemDelete((CFDictionaryRef)query);
|
||||
|
||||
keyData = key.toNSData();
|
||||
[query setObject:keyData forKey:(id)kSecValueData];
|
||||
|
||||
status = SecItemAdd((CFDictionaryRef)query, NULL);
|
||||
|
||||
if (status != noErr) {
|
||||
logger.error() << "Failed to store the key. Error:" << status;
|
||||
key = QByteArray();
|
||||
}
|
||||
|
||||
[query release];
|
||||
}
|
||||
|
||||
if (key.length() == CRYPTO_SETTINGS_KEY_SIZE) {
|
||||
memcpy(output, key.data(), CRYPTO_SETTINGS_KEY_SIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.error() << "Invalid key";
|
||||
#else
|
||||
Q_UNUSED(output);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
CryptoSettings::Version CryptoSettings::getSupportedVersion() {
|
||||
logger.debug() << "Get supported settings method";
|
||||
|
||||
#if defined(MVPN_IOS) || defined(MVPN_MACOS_NETWORKEXTENSION) || defined(MVPN_MACOS_DAEMON)
|
||||
uint8_t key[CRYPTO_SETTINGS_KEY_SIZE];
|
||||
if (getKey(key)) {
|
||||
logger.debug() << "Encryption supported!";
|
||||
return CryptoSettings::EncryptionChachaPolyV1;
|
||||
}
|
||||
#endif
|
||||
|
||||
logger.debug() << "No encryption";
|
||||
return CryptoSettings::NoEncryption;
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macosmenubar.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "mozillavpn.h"
|
||||
#include "qmlengineholder.h"
|
||||
#ifdef MVPN_MACOS
|
||||
# include "platforms/macos/macosutils.h"
|
||||
#endif
|
||||
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacOSManuBar");
|
||||
MacOSMenuBar* s_instance = nullptr;
|
||||
} // namespace
|
||||
|
||||
MacOSMenuBar::MacOSMenuBar() {
|
||||
MVPN_COUNT_CTOR(MacOSMenuBar);
|
||||
|
||||
Q_ASSERT(!s_instance);
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
MacOSMenuBar::~MacOSMenuBar() {
|
||||
MVPN_COUNT_DTOR(MacOSMenuBar);
|
||||
|
||||
Q_ASSERT(s_instance == this);
|
||||
s_instance = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
MacOSMenuBar* MacOSMenuBar::instance() {
|
||||
Q_ASSERT(s_instance);
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
void MacOSMenuBar::initialize() {
|
||||
logger.debug() << "Creating menubar";
|
||||
|
||||
AmneziaVPN* vpn = AmneziaVPN::instance();
|
||||
|
||||
m_menuBar = new QMenuBar(nullptr);
|
||||
|
||||
//% "File"
|
||||
QMenu* fileMenu = m_menuBar->addMenu(qtTrId("menubar.file.title"));
|
||||
|
||||
// Do not use qtTrId here!
|
||||
QAction* quit =
|
||||
fileMenu->addAction("quit", vpn->controller(), &Controller::quit);
|
||||
quit->setMenuRole(QAction::QuitRole);
|
||||
|
||||
// Do not use qtTrId here!
|
||||
m_aboutAction =
|
||||
fileMenu->addAction("about.vpn", vpn, &AmneziaVPN::requestAbout);
|
||||
m_aboutAction->setMenuRole(QAction::AboutRole);
|
||||
m_aboutAction->setVisible(vpn->state() == AmneziaVPN::StateMain);
|
||||
|
||||
// Do not use qtTrId here!
|
||||
m_preferencesAction =
|
||||
fileMenu->addAction("preferences", vpn, &AmneziaVPN::requestSettings);
|
||||
m_preferencesAction->setMenuRole(QAction::PreferencesRole);
|
||||
m_preferencesAction->setVisible(vpn->state() == AmneziaVPN::StateMain);
|
||||
|
||||
m_closeAction = fileMenu->addAction("close", []() {
|
||||
QmlEngineHolder::instance()->hideWindow();
|
||||
#ifdef MVPN_MACOS
|
||||
MacOSUtils::hideDockIcon();
|
||||
#endif
|
||||
});
|
||||
m_closeAction->setShortcut(QKeySequence::Close);
|
||||
|
||||
m_helpMenu = m_menuBar->addMenu("");
|
||||
|
||||
retranslate();
|
||||
};
|
||||
|
||||
void MacOSMenuBar::controllerStateChanged() {
|
||||
AmneziaVPN* vpn = AmneziaVPN::instance();
|
||||
m_preferencesAction->setVisible(vpn->state() == AmneziaVPN::StateMain);
|
||||
m_aboutAction->setVisible(vpn->state() == AmneziaVPN::StateMain);
|
||||
}
|
||||
|
||||
void MacOSMenuBar::retranslate() {
|
||||
logger.debug() << "Retranslate";
|
||||
|
||||
//% "Close"
|
||||
m_closeAction->setText(qtTrId("menubar.file.close"));
|
||||
|
||||
//% "Help"
|
||||
m_helpMenu->setTitle(qtTrId("menubar.help.title"));
|
||||
for (QAction* action : m_helpMenu->actions()) {
|
||||
m_helpMenu->removeAction(action);
|
||||
}
|
||||
|
||||
AmneziaVPN* vpn = AmneziaVPN::instance();
|
||||
vpn->helpModel()->forEach([&](const char* nameId, int id) {
|
||||
m_helpMenu->addAction(qtTrId(nameId),
|
||||
[help = vpn->helpModel(), id]() { help->open(id); });
|
||||
});
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MACOSMENUBAR_H
|
||||
#define MACOSMENUBAR_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QAction;
|
||||
class QMenu;
|
||||
class QMenuBar;
|
||||
|
||||
class MacOSMenuBar final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(MacOSMenuBar)
|
||||
|
||||
public:
|
||||
MacOSMenuBar();
|
||||
~MacOSMenuBar();
|
||||
|
||||
static MacOSMenuBar* instance();
|
||||
|
||||
void initialize();
|
||||
|
||||
void retranslate();
|
||||
|
||||
QMenuBar* menuBar() const { return m_menuBar; }
|
||||
|
||||
public slots:
|
||||
void controllerStateChanged();
|
||||
|
||||
private:
|
||||
QMenuBar* m_menuBar = nullptr;
|
||||
|
||||
QAction* m_aboutAction = nullptr;
|
||||
QAction* m_preferencesAction = nullptr;
|
||||
QAction* m_closeAction = nullptr;
|
||||
QMenu* m_helpMenu = nullptr;
|
||||
};
|
||||
|
||||
#endif // MACOSMENUBAR_H
|
||||
|
|
@ -5,19 +5,24 @@
|
|||
#ifndef MACOSNETWORKWATCHER_H
|
||||
#define MACOSNETWORKWATCHER_H
|
||||
|
||||
#import <Network/Network.h>
|
||||
|
||||
#include "../ios/iosnetworkwatcher.h"
|
||||
#include "networkwatcherimpl.h"
|
||||
|
||||
class MacOSNetworkWatcher final : public NetworkWatcherImpl {
|
||||
class QString;
|
||||
|
||||
class MacOSNetworkWatcher final : public IOSNetworkWatcher {
|
||||
public:
|
||||
MacOSNetworkWatcher(QObject* parent);
|
||||
~MacOSNetworkWatcher();
|
||||
|
||||
void initialize() override;
|
||||
|
||||
void start() override;
|
||||
|
||||
void checkInterface();
|
||||
|
||||
void controllerStateChanged();
|
||||
|
||||
private:
|
||||
void* m_delegate = nullptr;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@
|
|||
#include "logger.h"
|
||||
|
||||
#import <CoreWLAN/CoreWLAN.h>
|
||||
#import <Network/Network.h>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacOSNetworkWatcher");
|
||||
Logger logger("MacOSNetworkWatcher");
|
||||
}
|
||||
|
||||
@interface MacOSNetworkWatcherDelegate : NSObject <CWEventDelegate> {
|
||||
|
|
@ -37,13 +38,12 @@ Logger logger(LOG_MACOS, "MacOSNetworkWatcher");
|
|||
|
||||
@end
|
||||
|
||||
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) {
|
||||
MVPN_COUNT_CTOR(MacOSNetworkWatcher);
|
||||
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent) {
|
||||
MZ_COUNT_CTOR(MacOSNetworkWatcher);
|
||||
}
|
||||
|
||||
MacOSNetworkWatcher::~MacOSNetworkWatcher() {
|
||||
MVPN_COUNT_DTOR(MacOSNetworkWatcher);
|
||||
|
||||
MZ_COUNT_DTOR(MacOSNetworkWatcher);
|
||||
if (m_delegate) {
|
||||
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
|
||||
if (!client) {
|
||||
|
|
@ -57,10 +57,6 @@ MacOSNetworkWatcher::~MacOSNetworkWatcher() {
|
|||
}
|
||||
}
|
||||
|
||||
void MacOSNetworkWatcher::initialize() {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
void MacOSNetworkWatcher::start() {
|
||||
NetworkWatcherImpl::start();
|
||||
|
||||
|
|
@ -129,3 +125,4 @@ void MacOSNetworkWatcher::checkInterface() {
|
|||
|
||||
logger.debug() << "Secure WiFi interface";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,6 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macospingsender.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
|
|
@ -14,19 +10,26 @@
|
|||
#include <netinet/in_systm.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip_icmp.h>
|
||||
#include <sys/errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QSocketNotifier>
|
||||
#include <QtEndian>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger({LOG_MACOS, LOG_NETWORKING}, "MacOSPingSender");
|
||||
Logger logger("MacOSPingSender");
|
||||
|
||||
int identifier() { return (getpid() & 0xFFFF); }
|
||||
|
||||
}; // namespace
|
||||
|
||||
MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent)
|
||||
MacOSPingSender::MacOSPingSender(const QHostAddress& source, QObject* parent)
|
||||
: PingSender(parent) {
|
||||
MVPN_COUNT_CTOR(MacOSPingSender);
|
||||
MZ_COUNT_CTOR(MacOSPingSender);
|
||||
|
||||
if (getuid()) {
|
||||
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
|
||||
|
|
@ -38,15 +41,15 @@ MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent)
|
|||
return;
|
||||
}
|
||||
|
||||
quint32 ipv4addr = INADDR_ANY;
|
||||
if (!source.isNull()) {
|
||||
ipv4addr = source.toIPv4Address();
|
||||
}
|
||||
struct sockaddr_in addr;
|
||||
bzero(&addr, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_len = sizeof(addr);
|
||||
|
||||
if (inet_aton(source.toLocal8Bit().constData(), &addr.sin_addr) == 0) {
|
||||
logger.error() << "source address error";
|
||||
return;
|
||||
}
|
||||
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4addr);
|
||||
|
||||
if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
|
||||
logger.error() << "bind error:" << strerror(errno);
|
||||
|
|
@ -59,22 +62,19 @@ MacOSPingSender::MacOSPingSender(const QString& source, QObject* parent)
|
|||
}
|
||||
|
||||
MacOSPingSender::~MacOSPingSender() {
|
||||
MVPN_COUNT_DTOR(MacOSPingSender);
|
||||
MZ_COUNT_DTOR(MacOSPingSender);
|
||||
if (m_socket >= 0) {
|
||||
close(m_socket);
|
||||
}
|
||||
}
|
||||
|
||||
void MacOSPingSender::sendPing(const QString& dest, quint16 sequence) {
|
||||
void MacOSPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
|
||||
quint32 ipv4dest = dest.toIPv4Address();
|
||||
struct sockaddr_in addr;
|
||||
bzero(&addr, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_len = sizeof(addr);
|
||||
|
||||
if (inet_aton(dest.toLocal8Bit().constData(), &addr.sin_addr) == 0) {
|
||||
logger.error() << "DNS lookup failed";
|
||||
return;
|
||||
}
|
||||
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4dest);
|
||||
|
||||
struct icmp packet;
|
||||
bzero(&packet, sizeof packet);
|
||||
|
|
@ -86,6 +86,7 @@ void MacOSPingSender::sendPing(const QString& dest, quint16 sequence) {
|
|||
if (sendto(m_socket, (char*)&packet, sizeof(packet), 0,
|
||||
(struct sockaddr*)&addr, sizeof(addr)) != sizeof(packet)) {
|
||||
logger.error() << "ping sending failed:" << strerror(errno);
|
||||
emit criticalPingError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ class MacOSPingSender final : public PingSender {
|
|||
Q_DISABLE_COPY_MOVE(MacOSPingSender)
|
||||
|
||||
public:
|
||||
MacOSPingSender(const QString& source, QObject* parent = nullptr);
|
||||
MacOSPingSender(const QHostAddress& source, QObject* parent = nullptr);
|
||||
~MacOSPingSender();
|
||||
|
||||
void sendPing(const QString& dest, quint16 sequence) override;
|
||||
void sendPing(const QHostAddress& dest, quint16 sequence) override;
|
||||
|
||||
private slots:
|
||||
void socketReady();
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macosstartatbootwatcher.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "macosutils.h"
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacOSStartAtBootWatcher");
|
||||
}
|
||||
|
||||
MacOSStartAtBootWatcher::MacOSStartAtBootWatcher(bool startAtBoot) {
|
||||
MVPN_COUNT_CTOR(MacOSStartAtBootWatcher);
|
||||
|
||||
logger.debug() << "StartAtBoot watcher";
|
||||
MacOSUtils::enableLoginItem(startAtBoot);
|
||||
}
|
||||
|
||||
MacOSStartAtBootWatcher::~MacOSStartAtBootWatcher() {
|
||||
MVPN_COUNT_DTOR(MacOSStartAtBootWatcher);
|
||||
}
|
||||
|
||||
void MacOSStartAtBootWatcher::startAtBootChanged(bool startAtBoot) {
|
||||
logger.debug() << "StartAtBoot changed:" << startAtBoot;
|
||||
MacOSUtils::enableLoginItem(startAtBoot);
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MACOSSTARTATBOOTWATCHER_H
|
||||
#define MACOSSTARTATBOOTWATCHER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class MacOSStartAtBootWatcher final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(MacOSStartAtBootWatcher)
|
||||
|
||||
public:
|
||||
explicit MacOSStartAtBootWatcher(bool startAtBoot);
|
||||
~MacOSStartAtBootWatcher();
|
||||
|
||||
public slots:
|
||||
void startAtBootChanged(bool value);
|
||||
};
|
||||
|
||||
#endif // MACOSSTARTATBOOTWATCHER_H
|
||||
27
client/platforms/macos/macosstatusicon.h
Normal file
27
client/platforms/macos/macosstatusicon.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MACOSSTATUSICON_H
|
||||
#define MACOSSTATUSICON_H
|
||||
|
||||
#include <QMenu>
|
||||
#include <QObject>
|
||||
|
||||
class MacOSStatusIcon final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(MacOSStatusIcon)
|
||||
|
||||
public:
|
||||
explicit MacOSStatusIcon(QObject* parent);
|
||||
~MacOSStatusIcon();
|
||||
|
||||
public:
|
||||
void setIcon(const QString& iconUrl);
|
||||
void setIndicatorColor(const QColor& indicatorColor);
|
||||
void setMenu(NSMenu* statusBarMenu);
|
||||
void setToolTip(const QString& tooltip);
|
||||
void showMessage(const QString& title, const QString& message);
|
||||
};
|
||||
|
||||
#endif // MACOSSTATUSICON_H
|
||||
204
client/platforms/macos/macosstatusicon.mm
Normal file
204
client/platforms/macos/macosstatusicon.mm
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "macosstatusicon.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#import <QResource>
|
||||
|
||||
/**
|
||||
* Creates a NSStatusItem with that can hold an icon. Additionally a NSView is
|
||||
* set as a subview to the button item of the status item. The view serves as
|
||||
* an indicator that can be displayed in color eventhough the icon is set as a
|
||||
* template. In that way we give the system control over it’s effective
|
||||
* appearance.
|
||||
*/
|
||||
@interface MacOSStatusIconDelegate : NSObject
|
||||
@property(assign) NSStatusItem* statusItem;
|
||||
@property(assign) NSView* statusIndicator;
|
||||
|
||||
- (void)setIcon:(NSData*)imageData;
|
||||
- (void)setIndicator;
|
||||
- (void)setIndicatorColor:(NSColor*)color;
|
||||
- (void)setMenu:(NSMenu*)statusBarMenu;
|
||||
- (void)setToolTip:(NSString*)tooltip;
|
||||
@end
|
||||
|
||||
@implementation MacOSStatusIconDelegate
|
||||
/**
|
||||
* Initializes and sets the status item and indicator objects.
|
||||
*
|
||||
* @return An instance of MacOSStatusIconDelegate.
|
||||
*/
|
||||
- (id)init {
|
||||
self = [super init];
|
||||
|
||||
// Create status item
|
||||
self.statusItem =
|
||||
[[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
|
||||
self.statusItem.visible = true;
|
||||
// Add the indicator as a subview
|
||||
[self setIndicator];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image for the status icon.
|
||||
*
|
||||
* @param iconPath The data for the icon image.
|
||||
*/
|
||||
- (void)setIcon:(NSData*)imageData {
|
||||
NSImage* image = [[NSImage alloc] initWithData:imageData];
|
||||
[image setTemplate:true];
|
||||
|
||||
[self.statusItem.button setImage:image];
|
||||
[image release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds status indicator as a subview to the status item button.
|
||||
*/
|
||||
- (void)setIndicator {
|
||||
float viewHeight = NSHeight([self.statusItem.button bounds]);
|
||||
float dotSize = viewHeight * 0.35;
|
||||
float dotOrigin = (viewHeight - dotSize) * 0.8;
|
||||
|
||||
NSView* dot = [[NSView alloc] initWithFrame:NSMakeRect(dotOrigin, dotOrigin, dotSize, dotSize)];
|
||||
self.statusIndicator = dot;
|
||||
self.statusIndicator.wantsLayer = true;
|
||||
self.statusIndicator.layer.cornerRadius = dotSize * 0.5;
|
||||
|
||||
[self.statusItem.button addSubview:self.statusIndicator];
|
||||
[dot release];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color if the indicator.
|
||||
*
|
||||
* @param color The indicator background color.
|
||||
*/
|
||||
- (void)setIndicatorColor:(NSColor*)color {
|
||||
if (self.statusIndicator) {
|
||||
self.statusIndicator.layer.backgroundColor = color.CGColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status bar menu to the status item.
|
||||
*
|
||||
* @param statusBarMenu The menu object that is passed from QT.
|
||||
*/
|
||||
- (void)setMenu:(NSMenu*)statusBarMenu {
|
||||
[self.statusItem setMenu:statusBarMenu];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tooltip string for the status item.
|
||||
*
|
||||
* @param tooltip The tooltip string.
|
||||
*/
|
||||
- (void)setToolTip:(NSString*)tooltip {
|
||||
[self.statusItem.button setToolTip:tooltip];
|
||||
}
|
||||
@end
|
||||
|
||||
namespace {
|
||||
Logger logger("MacOSStatusIcon");
|
||||
|
||||
MacOSStatusIconDelegate* m_statusBarIcon = nullptr;
|
||||
}
|
||||
|
||||
MacOSStatusIcon::MacOSStatusIcon(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(MacOSStatusIcon);
|
||||
|
||||
logger.debug() << "Register delegate";
|
||||
Q_ASSERT(!m_statusBarIcon);
|
||||
|
||||
m_statusBarIcon = [[MacOSStatusIconDelegate alloc] init];
|
||||
}
|
||||
|
||||
MacOSStatusIcon::~MacOSStatusIcon() {
|
||||
MZ_COUNT_DTOR(MacOSStatusIcon);
|
||||
|
||||
logger.debug() << "Remove delegate";
|
||||
Q_ASSERT(m_statusBarIcon);
|
||||
|
||||
[static_cast<MacOSStatusIconDelegate*>(m_statusBarIcon) dealloc];
|
||||
m_statusBarIcon = nullptr;
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::setIcon(const QString& iconPath) {
|
||||
logger.debug() << "Set icon" << iconPath;
|
||||
|
||||
QResource imageResource = QResource(iconPath);
|
||||
Q_ASSERT(imageResource.isValid());
|
||||
|
||||
[m_statusBarIcon setIcon:imageResource.uncompressedData().toNSData()];
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
|
||||
logger.debug() << "Set indicator color";
|
||||
|
||||
if (!indicatorColor.isValid()) {
|
||||
[m_statusBarIcon setIndicatorColor:[NSColor clearColor]];
|
||||
return;
|
||||
}
|
||||
|
||||
NSColor* color = [NSColor colorWithCalibratedRed:indicatorColor.red() / 255.0f
|
||||
green:indicatorColor.green() / 255.0f
|
||||
blue:indicatorColor.blue() / 255.0f
|
||||
alpha:indicatorColor.alpha() / 255.0f];
|
||||
[m_statusBarIcon setIndicatorColor:color];
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::setMenu(NSMenu* statusBarMenu) {
|
||||
logger.debug() << "Set menu";
|
||||
[m_statusBarIcon setMenu:statusBarMenu];
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::setToolTip(const QString& tooltip) {
|
||||
logger.debug() << "Set tooltip";
|
||||
[m_statusBarIcon setToolTip:tooltip.toNSString()];
|
||||
}
|
||||
|
||||
void MacOSStatusIcon::showMessage(const QString& title, const QString& message) {
|
||||
logger.debug() << "Show message";
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
|
||||
// This is a no-op is authorization has been granted.
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
|
||||
UNAuthorizationOptionBadge)
|
||||
completionHandler:^(BOOL granted, NSError* _Nullable error) {
|
||||
if (error) {
|
||||
// Note: This error may happen if the application is not signed.
|
||||
NSLog(@"Error asking for permission to send notifications %@", error);
|
||||
return;
|
||||
}
|
||||
}];
|
||||
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
|
||||
content.title = [title.toNSString() autorelease];
|
||||
content.body = [message.toNSString() autorelease];
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
|
||||
UNTimeIntervalNotificationTrigger* trigger =
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO];
|
||||
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
|
||||
content:content
|
||||
trigger:trigger];
|
||||
|
||||
[center addNotificationRequest:request
|
||||
withCompletionHandler:^(NSError* _Nullable error) {
|
||||
if (error) {
|
||||
logger.error() << "Local Notification failed" << error;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
|
@ -10,14 +10,19 @@
|
|||
|
||||
class MacOSUtils final {
|
||||
public:
|
||||
static NSString* appId();
|
||||
|
||||
static QString computerName();
|
||||
|
||||
static void enableLoginItem(bool startAtBoot);
|
||||
|
||||
static void setDockClickHandler();
|
||||
static void setStatusBarTextColor();
|
||||
|
||||
static void hideDockIcon();
|
||||
static void showDockIcon();
|
||||
|
||||
static void patchNSStatusBarSetImageForBigSur();
|
||||
};
|
||||
|
||||
#endif // MACOSUTILS_H
|
||||
|
|
|
|||
|
|
@ -4,20 +4,27 @@
|
|||
|
||||
#include "macosutils.h"
|
||||
#include "logger.h"
|
||||
#include "models/helpmodel.h"
|
||||
#include "qmlengineholder.h"
|
||||
|
||||
#include <objc/message.h>
|
||||
#include <objc/objc.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QMenuBar>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <ServiceManagement/ServiceManagement.h>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacOSUtils");
|
||||
Logger logger("MacOSUtils");
|
||||
}
|
||||
|
||||
// static
|
||||
NSString* MacOSUtils::appId() {
|
||||
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
|
||||
if (!appId) {
|
||||
// Fallback. When an unsigned/un-notarized app is executed in
|
||||
// command-line mode, it could fail the fetching of its own bundle id.
|
||||
appId = @"org.amnezia.AmneziaVPN";
|
||||
}
|
||||
|
||||
return appId;
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
@ -30,7 +37,9 @@ QString MacOSUtils::computerName() {
|
|||
void MacOSUtils::enableLoginItem(bool startAtBoot) {
|
||||
logger.debug() << "Enabling login-item";
|
||||
|
||||
NSString* appId = [[NSBundle mainBundle] bundleIdentifier];
|
||||
NSString* appId = MacOSUtils::appId();
|
||||
Q_ASSERT(appId);
|
||||
|
||||
NSString* loginItemAppId =
|
||||
QString("%1.login-item").arg(QString::fromNSString(appId)).toNSString();
|
||||
CFStringRef cfs = (__bridge CFStringRef)loginItemAppId;
|
||||
|
|
@ -46,7 +55,8 @@ bool dockClickHandler(id self, SEL cmd, ...) {
|
|||
Q_UNUSED(cmd);
|
||||
|
||||
logger.debug() << "Dock icon clicked.";
|
||||
QmlEngineHolder::instance()->showWindow();
|
||||
//TODO IMPL FOR AMNEZIA
|
||||
//QmlEngineHolder::instance()->showWindow();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
|
@ -89,3 +99,102 @@ void MacOSUtils::hideDockIcon() {
|
|||
void MacOSUtils::showDockIcon() {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the setImage method on NSStatusBarButton with a method that scales
|
||||
* images proportionally before setting.
|
||||
*
|
||||
* The reason for this is that there is a bug in Qt 5.15 that causes status bar
|
||||
* icons to be displayed larger than UI recommendations, and out of proportion
|
||||
* on displays with a device pixel ratio greater than 1 (MacOS Big Sur only).
|
||||
* This bug will not be fixed in Qt open source versions, so we have to resort
|
||||
* to a hack that exchanges the implementation of a method on NSStatusBarButton
|
||||
* with one that correctly scales the icon.
|
||||
*
|
||||
* Original bug (and sample implementation):
|
||||
* https://bugreports.qt.io/browse/QTBUG-88600
|
||||
*/
|
||||
void MacOSUtils::patchNSStatusBarSetImageForBigSur() {
|
||||
Method original = class_getInstanceMethod([NSStatusBarButton class], @selector(setImage:));
|
||||
Method patched = class_getInstanceMethod([NSStatusBarButton class], @selector(setImagePatched:));
|
||||
method_exchangeImplementations(original, patched);
|
||||
}
|
||||
|
||||
@interface NSImageScalingHelper : NSObject
|
||||
/**
|
||||
* Create a proportionally scaled image according to the given target size.
|
||||
*
|
||||
* @param sourceImage The original image to be scaled.
|
||||
* @param targetSize The required size of the image.
|
||||
* @return A scaled image.
|
||||
*/
|
||||
+ (NSImage*)imageByScaling:(NSImage*)sourceImage size:(NSSize)targetSize;
|
||||
@end
|
||||
|
||||
@implementation NSImageScalingHelper
|
||||
+ (NSImage*)imageByScaling:(NSImage*)sourceImage size:(NSSize)targetSize {
|
||||
NSImage* newImage = nil;
|
||||
|
||||
if ([sourceImage isValid]) {
|
||||
NSSize sourceSize = [sourceImage size];
|
||||
|
||||
if (sourceSize.width != 0.0 && sourceSize.height != 0.0) {
|
||||
float scaleFactor = 0.0;
|
||||
float scaledWidth = targetSize.width;
|
||||
float scaledHeight = targetSize.height;
|
||||
|
||||
NSPoint thumbnailPoint = NSZeroPoint;
|
||||
|
||||
if (NSEqualSizes(sourceSize, targetSize) == NO) {
|
||||
float widthFactor = targetSize.width / sourceSize.width;
|
||||
float heightFactor = targetSize.height / sourceSize.height;
|
||||
|
||||
if (widthFactor < heightFactor) {
|
||||
scaleFactor = widthFactor;
|
||||
} else {
|
||||
scaleFactor = heightFactor;
|
||||
}
|
||||
scaledWidth = sourceSize.width * scaleFactor;
|
||||
scaledHeight = sourceSize.height * scaleFactor;
|
||||
|
||||
if (widthFactor < heightFactor) {
|
||||
thumbnailPoint.y = (targetSize.height - scaledHeight) * 0.5;
|
||||
} else {
|
||||
thumbnailPoint.x = (targetSize.width - scaledWidth) * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
newImage = [[NSImage alloc] initWithSize:targetSize];
|
||||
|
||||
[newImage lockFocus];
|
||||
|
||||
NSRect thumbnailRect;
|
||||
thumbnailRect.origin = thumbnailPoint;
|
||||
thumbnailRect.size.width = scaledWidth;
|
||||
thumbnailRect.size.height = scaledHeight;
|
||||
[sourceImage drawInRect:thumbnailRect
|
||||
fromRect:NSZeroRect
|
||||
operation:NSCompositingOperationSourceOver
|
||||
fraction:1.0];
|
||||
|
||||
[newImage unlockFocus];
|
||||
|
||||
[newImage setTemplate:[sourceImage isTemplate]];
|
||||
}
|
||||
}
|
||||
return [newImage autorelease];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation NSStatusBarButton (Swizzle)
|
||||
- (void)setImagePatched:(NSImage*)image {
|
||||
NSImage* img = image;
|
||||
|
||||
if (image != nil) {
|
||||
int thickness = [[NSStatusBar systemStatusBar] thickness];
|
||||
img = [NSImageScalingHelper imageByScaling:image size:NSMakeSize(thickness, thickness)];
|
||||
}
|
||||
|
||||
[self setImagePatched:img];
|
||||
}
|
||||
@end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue