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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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