iOS Wireguard
This commit is contained in:
parent
421f665e85
commit
7701efc704
117 changed files with 6577 additions and 0 deletions
222
client/platforms/macos/daemon/dnsutilsmacos.cpp
Normal file
222
client/platforms/macos/daemon/dnsutilsmacos.cpp
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/* 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 "dnsutilsmacos.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <systemconfiguration/scpreferences.h>
|
||||
#include <systemconfiguration/scdynamicstore.h>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "DnsUtilsMacos");
|
||||
}
|
||||
|
||||
DnsUtilsMacos::DnsUtilsMacos(QObject* parent) : DnsUtils(parent) {
|
||||
MVPN_COUNT_CTOR(DnsUtilsMacos);
|
||||
|
||||
m_scStore = SCDynamicStoreCreate(kCFAllocatorSystemDefault,
|
||||
CFSTR("mozillavpn"), nullptr, nullptr);
|
||||
if (m_scStore == nullptr) {
|
||||
logger.error() << "Failed to create system configuration store ref";
|
||||
}
|
||||
|
||||
logger.debug() << "DnsUtilsMacos created.";
|
||||
}
|
||||
|
||||
DnsUtilsMacos::~DnsUtilsMacos() {
|
||||
MVPN_COUNT_DTOR(DnsUtilsMacos);
|
||||
restoreResolvers();
|
||||
logger.debug() << "DnsUtilsMacos destroyed.";
|
||||
}
|
||||
|
||||
static QString cfParseString(CFTypeRef ref) {
|
||||
if (CFGetTypeID(ref) != CFStringGetTypeID()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
CFStringRef stringref = (CFStringRef)ref;
|
||||
CFRange range;
|
||||
range.location = 0;
|
||||
range.length = CFStringGetLength(stringref);
|
||||
if (range.length <= 0) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
UniChar* buf = (UniChar*)malloc(range.length * sizeof(UniChar));
|
||||
if (!buf) {
|
||||
return QString();
|
||||
}
|
||||
auto guard = qScopeGuard([&] { free(buf); });
|
||||
|
||||
CFStringGetCharacters(stringref, range, buf);
|
||||
return QString::fromUtf16(buf, range.length);
|
||||
}
|
||||
|
||||
static QStringList cfParseStringList(CFTypeRef ref) {
|
||||
if (CFGetTypeID(ref) != CFArrayGetTypeID()) {
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
CFArrayRef array = (CFArrayRef)ref;
|
||||
QStringList result;
|
||||
for (CFIndex i = 0; i < CFArrayGetCount(array); i++) {
|
||||
CFTypeRef value = CFArrayGetValueAtIndex(array, i);
|
||||
result.append(cfParseString(value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void cfDictSetString(CFMutableDictionaryRef dict, CFStringRef name,
|
||||
const QString& value) {
|
||||
if (value.isNull()) {
|
||||
return;
|
||||
}
|
||||
CFStringRef cfValue = CFStringCreateWithCString(
|
||||
kCFAllocatorSystemDefault, qUtf8Printable(value), kCFStringEncodingUTF8);
|
||||
CFDictionarySetValue(dict, name, cfValue);
|
||||
CFRelease(cfValue);
|
||||
}
|
||||
|
||||
static void cfDictSetStringList(CFMutableDictionaryRef dict, CFStringRef name,
|
||||
const QStringList& valueList) {
|
||||
if (valueList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CFMutableArrayRef array;
|
||||
array = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0,
|
||||
&kCFTypeArrayCallBacks);
|
||||
if (array == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QString& rstring : valueList) {
|
||||
CFStringRef cfAddr = CFStringCreateWithCString(kCFAllocatorSystemDefault,
|
||||
qUtf8Printable(rstring),
|
||||
kCFStringEncodingUTF8);
|
||||
CFArrayAppendValue(array, cfAddr);
|
||||
CFRelease(cfAddr);
|
||||
}
|
||||
CFDictionarySetValue(dict, name, array);
|
||||
CFRelease(array);
|
||||
}
|
||||
|
||||
bool DnsUtilsMacos::updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
Q_UNUSED(ifname);
|
||||
|
||||
// Get the list of current network services.
|
||||
CFArrayRef netServices = SCDynamicStoreCopyKeyList(
|
||||
m_scStore, CFSTR("Setup:/Network/Service/[0-9A-F-]+"));
|
||||
if (netServices == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto serviceGuard = qScopeGuard([&] { CFRelease(netServices); });
|
||||
|
||||
// Prepare the DNS configuration.
|
||||
CFMutableDictionaryRef dnsConfig = CFDictionaryCreateMutable(
|
||||
kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
auto configGuard = qScopeGuard([&] { CFRelease(dnsConfig); });
|
||||
QStringList list;
|
||||
for (const QHostAddress& addr : resolvers) {
|
||||
list.append(addr.toString());
|
||||
}
|
||||
cfDictSetStringList(dnsConfig, kSCPropNetDNSServerAddresses, list);
|
||||
cfDictSetString(dnsConfig, kSCPropNetDNSDomainName, "lan");
|
||||
|
||||
// Backup each network service's DNS config, and replace it with ours.
|
||||
for (CFIndex i = 0; i < CFArrayGetCount(netServices); i++) {
|
||||
QString service = cfParseString(CFArrayGetValueAtIndex(netServices, i));
|
||||
QString uuid = service.section('/', 3, 3);
|
||||
if (uuid.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
backupService(uuid);
|
||||
|
||||
logger.debug() << "Setting DNS config for" << uuid;
|
||||
CFStringRef dnsPath = CFStringCreateWithFormat(
|
||||
kCFAllocatorSystemDefault, nullptr,
|
||||
CFSTR("Setup:/Network/Service/%s/DNS"), qPrintable(uuid));
|
||||
if (!dnsPath) {
|
||||
continue;
|
||||
}
|
||||
SCDynamicStoreSetValue(m_scStore, dnsPath, dnsConfig);
|
||||
CFRelease(dnsPath);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DnsUtilsMacos::restoreResolvers() {
|
||||
for (const QString& uuid : m_prevServices.keys()) {
|
||||
CFStringRef path = CFStringCreateWithFormat(
|
||||
kCFAllocatorSystemDefault, nullptr,
|
||||
CFSTR("Setup:/Network/Service/%s/DNS"), qPrintable(uuid));
|
||||
|
||||
logger.debug() << "Restoring DNS config for" << uuid;
|
||||
const DnsBackup& backup = m_prevServices[uuid];
|
||||
if (backup.isValid()) {
|
||||
CFMutableDictionaryRef config;
|
||||
config = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0,
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
cfDictSetString(config, kSCPropNetDNSDomainName, backup.m_domain);
|
||||
cfDictSetStringList(config, kSCPropNetDNSSearchDomains, backup.m_search);
|
||||
cfDictSetStringList(config, kSCPropNetDNSServerAddresses,
|
||||
backup.m_servers);
|
||||
cfDictSetStringList(config, kSCPropNetDNSSortList, backup.m_sortlist);
|
||||
SCDynamicStoreSetValue(m_scStore, path, config);
|
||||
CFRelease(config);
|
||||
} else {
|
||||
SCDynamicStoreRemoveValue(m_scStore, path);
|
||||
}
|
||||
CFRelease(path);
|
||||
}
|
||||
|
||||
m_prevServices.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DnsUtilsMacos::backupService(const QString& uuid) {
|
||||
DnsBackup backup;
|
||||
CFStringRef path = CFStringCreateWithFormat(
|
||||
kCFAllocatorSystemDefault, nullptr,
|
||||
CFSTR("Setup:/Network/Service/%s/DNS"), qPrintable(uuid));
|
||||
CFDictionaryRef config =
|
||||
(CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore, path);
|
||||
auto serviceGuard = qScopeGuard([&] {
|
||||
if (config) {
|
||||
CFRelease(config);
|
||||
}
|
||||
CFRelease(path);
|
||||
});
|
||||
|
||||
// Parse the DNS protocol entry and save it for later.
|
||||
if (config) {
|
||||
CFTypeRef value;
|
||||
value = CFDictionaryGetValue(config, kSCPropNetDNSDomainName);
|
||||
if (value) {
|
||||
backup.m_domain = cfParseString(value);
|
||||
}
|
||||
value = CFDictionaryGetValue(config, kSCPropNetDNSServerAddresses);
|
||||
if (value) {
|
||||
backup.m_servers = cfParseStringList(value);
|
||||
}
|
||||
value = CFDictionaryGetValue(config, kSCPropNetDNSSearchDomains);
|
||||
if (value) {
|
||||
backup.m_search = cfParseStringList(value);
|
||||
}
|
||||
value = CFDictionaryGetValue(config, kSCPropNetDNSSortList);
|
||||
if (value) {
|
||||
backup.m_sortlist = cfParseStringList(value);
|
||||
}
|
||||
}
|
||||
|
||||
m_prevServices[uuid] = backup;
|
||||
}
|
||||
51
client/platforms/macos/daemon/dnsutilsmacos.h
Normal file
51
client/platforms/macos/daemon/dnsutilsmacos.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* 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 DNSUTILSMACOS_H
|
||||
#define DNSUTILSMACOS_H
|
||||
|
||||
#include "dnsutils.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include <systemconfiguration/scdynamicstore.h>
|
||||
#include <systemconfiguration/systemconfiguration.h>
|
||||
|
||||
class DnsUtilsMacos final : public DnsUtils {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DnsUtilsMacos)
|
||||
|
||||
public:
|
||||
explicit DnsUtilsMacos(QObject* parent);
|
||||
virtual ~DnsUtilsMacos();
|
||||
bool updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) override;
|
||||
bool restoreResolvers() override;
|
||||
|
||||
private:
|
||||
void backupResolvers();
|
||||
void backupService(const QString& uuid);
|
||||
|
||||
private:
|
||||
class DnsBackup {
|
||||
public:
|
||||
DnsBackup() {}
|
||||
bool isValid() const {
|
||||
return !m_domain.isEmpty() || !m_search.isEmpty() ||
|
||||
!m_servers.isEmpty() || !m_sortlist.isEmpty();
|
||||
}
|
||||
|
||||
QString m_domain;
|
||||
QStringList m_search;
|
||||
QStringList m_servers;
|
||||
QStringList m_sortlist;
|
||||
};
|
||||
|
||||
SCDynamicStoreRef m_scStore = nullptr;
|
||||
QMap<QString, DnsBackup> m_prevServices;
|
||||
};
|
||||
|
||||
#endif // DNSUTILSMACOS_H
|
||||
180
client/platforms/macos/daemon/iputilsmacos.cpp
Normal file
180
client/platforms/macos/daemon/iputilsmacos.cpp
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/* 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 "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>
|
||||
#include <net/if_var.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/in_var.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
constexpr uint32_t ETH_MTU = 1500;
|
||||
constexpr uint32_t WG_MTU_OVERHEAD = 80;
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "IPUtilsMacos");
|
||||
}
|
||||
|
||||
IPUtilsMacos::IPUtilsMacos(QObject* parent) : IPUtils(parent) {
|
||||
MVPN_COUNT_CTOR(IPUtilsMacos);
|
||||
logger.debug() << "IPUtilsMacos created.";
|
||||
}
|
||||
|
||||
IPUtilsMacos::~IPUtilsMacos() {
|
||||
MVPN_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;
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
struct ifreq ifr;
|
||||
|
||||
// Create socket file descriptor to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// MTU
|
||||
strncpy(ifr.ifr_name, qPrintable(ifname), IFNAMSIZ);
|
||||
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
|
||||
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set MTU:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the interface flags
|
||||
strncpy(ifr.ifr_name, qPrintable(ifname), IFNAMSIZ);
|
||||
ret = ioctl(sockfd, SIOCGIFFLAGS, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to get interface flags:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Up
|
||||
ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
|
||||
ret = ioctl(sockfd, SIOCSIFFLAGS, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set device up:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
struct ifaliasreq ifr;
|
||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
||||
struct sockaddr_in* ifrMask = (struct sockaddr_in*)&ifr.ifra_mask;
|
||||
struct sockaddr_in* ifrBcast = (struct sockaddr_in*)&ifr.ifra_broadaddr;
|
||||
|
||||
// Name the interface and set family
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifra_name, qPrintable(ifname), IFNAMSIZ);
|
||||
|
||||
// Get the device address to add to interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv4Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
ifrAddr->sin_family = AF_INET;
|
||||
ifrAddr->sin_len = sizeof(struct sockaddr_in);
|
||||
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
|
||||
|
||||
// Set the netmask to /32
|
||||
ifrMask->sin_family = AF_INET;
|
||||
ifrMask->sin_len = sizeof(struct sockaddr_in);
|
||||
memset(&ifrMask->sin_addr, 0xff, sizeof(ifrMask->sin_addr));
|
||||
|
||||
// Set the broadcast address.
|
||||
ifrBcast->sin_family = AF_INET;
|
||||
ifrBcast->sin_len = sizeof(struct sockaddr_in);
|
||||
ifrBcast->sin_addr.s_addr =
|
||||
(ifrAddr->sin_addr.s_addr | ~ifrMask->sin_addr.s_addr);
|
||||
|
||||
// Create an IPv4 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCAIFADDR, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv4: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
struct in6_aliasreq ifr6;
|
||||
|
||||
// Name the interface and set family
|
||||
memset(&ifr6, 0, sizeof(ifr6));
|
||||
strncpy(ifr6.ifra_name, qPrintable(ifname), IFNAMSIZ);
|
||||
ifr6.ifra_addr.sin6_family = AF_INET6;
|
||||
ifr6.ifra_addr.sin6_len = sizeof(ifr6.ifra_addr);
|
||||
ifr6.ifra_lifetime.ia6t_vltime = ifr6.ifra_lifetime.ia6t_pltime = 0xffffffff;
|
||||
ifr6.ifra_prefixmask.sin6_family = AF_INET6;
|
||||
ifr6.ifra_prefixmask.sin6_len = sizeof(ifr6.ifra_prefixmask);
|
||||
memset(&ifr6.ifra_prefixmask.sin6_addr, 0xff, sizeof(struct in6_addr));
|
||||
|
||||
// Get the device address to add to interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv6Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
inet_pton(AF_INET6, deviceAddr, &ifr6.ifra_addr.sin6_addr);
|
||||
|
||||
// Create IPv4 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv6: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
28
client/platforms/macos/daemon/iputilsmacos.h
Normal file
28
client/platforms/macos/daemon/iputilsmacos.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* 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 IPUTILSMACOS_H
|
||||
#define IPUTILSMACOS_H
|
||||
|
||||
#include "daemon/iputils.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
class IPUtilsMacos final : public IPUtils {
|
||||
public:
|
||||
IPUtilsMacos(QObject* parent);
|
||||
~IPUtilsMacos();
|
||||
bool addInterfaceIPs(const InterfaceConfig& config) override;
|
||||
bool setMTUAndUp(const InterfaceConfig& config) override;
|
||||
void setIfname(const QString& ifname) { m_ifname = ifname; }
|
||||
|
||||
private:
|
||||
bool addIP4AddressToDevice(const InterfaceConfig& config);
|
||||
bool addIP6AddressToDevice(const InterfaceConfig& config);
|
||||
|
||||
private:
|
||||
QString m_ifname;
|
||||
};
|
||||
|
||||
#endif // IPUTILSMACOS_H
|
||||
74
client/platforms/macos/daemon/macosdaemon.cpp
Normal file
74
client/platforms/macos/daemon/macosdaemon.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/* 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 "macosdaemon.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "wgquickprocess.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QLocalSocket>
|
||||
#include <QProcess>
|
||||
#include <QSettings>
|
||||
#include <QTextStream>
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_MACOS, "MacOSDaemon");
|
||||
MacOSDaemon* s_daemon = nullptr;
|
||||
} // namespace
|
||||
|
||||
MacOSDaemon::MacOSDaemon() : Daemon(nullptr) {
|
||||
MVPN_COUNT_CTOR(MacOSDaemon);
|
||||
|
||||
logger.debug() << "Daemon created";
|
||||
|
||||
m_wgutils = new WireguardUtilsMacos(this);
|
||||
m_dnsutils = new DnsUtilsMacos(this);
|
||||
m_iputils = new IPUtilsMacos(this);
|
||||
|
||||
Q_ASSERT(s_daemon == nullptr);
|
||||
s_daemon = this;
|
||||
}
|
||||
|
||||
MacOSDaemon::~MacOSDaemon() {
|
||||
MVPN_COUNT_DTOR(MacOSDaemon);
|
||||
|
||||
logger.debug() << "Daemon released";
|
||||
|
||||
Q_ASSERT(s_daemon == this);
|
||||
s_daemon = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
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);
|
||||
}
|
||||
37
client/platforms/macos/daemon/macosdaemon.h
Normal file
37
client/platforms/macos/daemon/macosdaemon.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* 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 MACOSDAEMON_H
|
||||
#define MACOSDAEMON_H
|
||||
|
||||
#include "daemon.h"
|
||||
#include "dnsutilsmacos.h"
|
||||
#include "iputilsmacos.h"
|
||||
#include "wireguardutilsmacos.h"
|
||||
|
||||
class MacOSDaemon final : public Daemon {
|
||||
friend class IPUtilsMacos;
|
||||
|
||||
public:
|
||||
MacOSDaemon();
|
||||
~MacOSDaemon();
|
||||
|
||||
static MacOSDaemon* instance();
|
||||
|
||||
QByteArray getStatus() override;
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
bool supportDnsUtils() const override { return true; }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
bool supportIPUtils() const override { return true; }
|
||||
IPUtils* iputils() override { return m_iputils; }
|
||||
|
||||
private:
|
||||
WireguardUtilsMacos* m_wgutils = nullptr;
|
||||
DnsUtilsMacos* m_dnsutils = nullptr;
|
||||
IPUtilsMacos* m_iputils = nullptr;
|
||||
};
|
||||
|
||||
#endif // MACOSDAEMON_H
|
||||
60
client/platforms/macos/daemon/macosdaemonserver.cpp
Normal file
60
client/platforms/macos/daemon/macosdaemonserver.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/* 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;
|
||||
18
client/platforms/macos/daemon/macosdaemonserver.h
Normal file
18
client/platforms/macos/daemon/macosdaemonserver.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef 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
|
||||
354
client/platforms/macos/daemon/macosroutemonitor.cpp
Normal file
354
client/platforms/macos/daemon/macosroutemonitor.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/* 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 "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>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <net/if_types.h>
|
||||
#include <net/route.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent)
|
||||
: QObject(parent), m_ifname(ifname) {
|
||||
MVPN_COUNT_CTOR(MacosRouteMonitor);
|
||||
logger.debug() << "MacosRouteMonitor created.";
|
||||
|
||||
m_rtsock = socket(PF_ROUTE, SOCK_RAW, 0);
|
||||
if (m_rtsock < 0) {
|
||||
logger.error() << "Failed to create routing socket:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable replies to our own messages.
|
||||
int off = 0;
|
||||
setsockopt(m_rtsock, SOL_SOCKET, SO_USELOOPBACK, &off, sizeof(off));
|
||||
|
||||
m_notifier = new QSocketNotifier(m_rtsock, QSocketNotifier::Read, this);
|
||||
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||
&MacosRouteMonitor::rtsockReady);
|
||||
}
|
||||
|
||||
MacosRouteMonitor::~MacosRouteMonitor() {
|
||||
MVPN_COUNT_DTOR(MacosRouteMonitor);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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(" ");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
QStringList list;
|
||||
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(" ");
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::handleRtmChange(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;
|
||||
}
|
||||
}
|
||||
|
||||
QStringList list;
|
||||
for (auto addr : addrlist) {
|
||||
list.append(addrToString(addr));
|
||||
}
|
||||
logger.debug() << "Route chagned by" << rtm->rtm_pid
|
||||
<< QString("addrs(%1):").arg(rtm->rtm_addrs) << list.join(" ");
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::handleIfaceInfo(const struct if_msghdr* ifm,
|
||||
const QByteArray& payload) {
|
||||
QList<QByteArray> addrlist = parseAddrList(payload);
|
||||
|
||||
if (ifm->ifm_index != if_nametoindex(qPrintable(m_ifname))) {
|
||||
return;
|
||||
}
|
||||
m_ifflags = ifm->ifm_flags;
|
||||
|
||||
QStringList list;
|
||||
for (auto addr : addrlist) {
|
||||
list.append(addrToString(addr));
|
||||
}
|
||||
logger.debug() << "Interface " << ifm->ifm_index
|
||||
<< "chagned flags:" << ifm->ifm_flags
|
||||
<< QString("addrs(%1):").arg(ifm->ifm_addrs) << list.join(" ");
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::rtsockReady() {
|
||||
char buf[1024];
|
||||
ssize_t len = recv(m_rtsock, buf, sizeof(buf), MSG_DONTWAIT);
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifndef RTMSG_NEXT
|
||||
# define RTMSG_NEXT(_rtm_) \
|
||||
(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]);
|
||||
while (rtm < end) {
|
||||
// Ensure the message fits within the buffer
|
||||
if (RTMSG_NEXT(rtm) > end) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle the routing message.
|
||||
QByteArray message((char*)rtm, rtm->rtm_msglen);
|
||||
switch (rtm->rtm_type) {
|
||||
case RTM_ADD:
|
||||
message.remove(0, sizeof(struct rt_msghdr));
|
||||
handleRtmAdd(rtm, message);
|
||||
break;
|
||||
case RTM_DELETE:
|
||||
message.remove(0, sizeof(struct rt_msghdr));
|
||||
handleRtmDelete(rtm, message);
|
||||
break;
|
||||
case RTM_CHANGE:
|
||||
message.remove(0, sizeof(struct rt_msghdr));
|
||||
handleRtmChange(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;
|
||||
}
|
||||
|
||||
rtm = RTMSG_NEXT(rtm);
|
||||
}
|
||||
}
|
||||
|
||||
void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
|
||||
int rtaddr, const void* sa) {
|
||||
size_t sa_len = ((struct sockaddr*)sa)->sa_len;
|
||||
Q_ASSERT((rtm->rtm_addrs & rtaddr) == 0);
|
||||
if ((rtm->rtm_msglen + sa_len) > maxlen) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy((char*)rtm + rtm->rtm_msglen, sa, sa_len);
|
||||
rtm->rtm_addrs |= rtaddr;
|
||||
rtm->rtm_msglen += sa_len;
|
||||
if (rtm->rtm_msglen % sizeof(uint32_t)) {
|
||||
rtm->rtm_msglen += sizeof(uint32_t) - (rtm->rtm_msglen % sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddressRange& prefix) {
|
||||
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;
|
||||
|
||||
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_flags = RTF_STATIC | RTF_UP;
|
||||
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));
|
||||
|
||||
// Append RTA_DST
|
||||
if (prefix.type() == IPAddressRange::IPv6) {
|
||||
struct sockaddr_in6 sin6;
|
||||
Q_IPV6ADDR dst = QHostAddress(prefix.ipAddress()).toIPv6Address();
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_len = sizeof(sin6);
|
||||
memcpy(&sin6.sin6_addr, &dst, 16);
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin6);
|
||||
} else {
|
||||
struct sockaddr_in sin;
|
||||
quint32 dst = QHostAddress(prefix.ipAddress()).toIPv4Address();
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_len = sizeof(sin);
|
||||
sin.sin_addr.s_addr = htonl(dst);
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_DST, &sin);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Append RTA_NETMASK
|
||||
int plen = prefix.range();
|
||||
if (prefix.type() == IPAddressRange::IPv6) {
|
||||
struct sockaddr_in6 sin6;
|
||||
memset(&sin6, 0, sizeof(sin6));
|
||||
sin6.sin6_family = AF_INET6;
|
||||
sin6.sin6_len = sizeof(sin6);
|
||||
memset(&sin6.sin6_addr.s6_addr, 0xff, plen / 8);
|
||||
if (plen % 8) {
|
||||
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) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_len = sizeof(struct sockaddr_in);
|
||||
sin.sin_addr.s_addr = 0xffffffff;
|
||||
if (plen < 32) {
|
||||
sin.sin_addr.s_addr ^= htonl(0xffffffff >> plen);
|
||||
}
|
||||
rtmAppendAddr(rtm, rtm_max_size, RTA_NETMASK, &sin);
|
||||
}
|
||||
|
||||
// Send the routing message to the kernel.
|
||||
int len = write(m_rtsock, rtm, rtm->rtm_msglen);
|
||||
if (len == rtm->rtm_msglen) {
|
||||
return true;
|
||||
}
|
||||
if ((action == RTM_ADD) && (errno == EEXIST)) {
|
||||
return true;
|
||||
}
|
||||
if ((action == RTM_DELETE) && (errno == ESRCH)) {
|
||||
return true;
|
||||
}
|
||||
logger.warning() << "Failed to send routing message:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::insertRoute(const IPAddressRange& prefix) {
|
||||
return rtmSendRoute(RTM_ADD, prefix);
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::deleteRoute(const IPAddressRange& prefix) {
|
||||
return rtmSendRoute(RTM_DELETE, prefix);
|
||||
}
|
||||
|
||||
// static
|
||||
QList<QByteArray> MacosRouteMonitor::parseAddrList(const QByteArray& payload) {
|
||||
QList<QByteArray> list;
|
||||
int offset = 0;
|
||||
constexpr int minlen = offsetof(struct sockaddr, sa_len) + sizeof(u_short);
|
||||
|
||||
while ((offset + minlen) <= payload.length()) {
|
||||
struct sockaddr* sa = (struct sockaddr*)(payload.constData() + offset);
|
||||
int paddedSize = sa->sa_len;
|
||||
if (!paddedSize || (paddedSize % sizeof(uint32_t))) {
|
||||
paddedSize += sizeof(uint32_t) - (paddedSize % sizeof(uint32_t));
|
||||
}
|
||||
if ((offset + paddedSize) > payload.length()) {
|
||||
break;
|
||||
}
|
||||
list.append(payload.mid(offset, paddedSize));
|
||||
offset += paddedSize;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// static
|
||||
QString MacosRouteMonitor::addrToString(const struct sockaddr* sa) {
|
||||
if (sa->sa_family == AF_INET) {
|
||||
const struct sockaddr_in* sin = (const struct sockaddr_in*)sa;
|
||||
return QString(inet_ntoa(sin->sin_addr));
|
||||
}
|
||||
if (sa->sa_family == AF_INET6) {
|
||||
const struct sockaddr_in6* sin6 = (const struct sockaddr_in6*)sa;
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
return QString(inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf)));
|
||||
}
|
||||
if (sa->sa_family == AF_LINK) {
|
||||
const struct sockaddr_dl* sdl = (const struct sockaddr_dl*)sa;
|
||||
return QString(link_ntoa(sdl));
|
||||
}
|
||||
return QString("unknown(af=%1)").arg(sa->sa_family);
|
||||
}
|
||||
|
||||
// static
|
||||
QString MacosRouteMonitor::addrToString(const QByteArray& data) {
|
||||
const struct sockaddr* sa = (const struct sockaddr*)data.constData();
|
||||
Q_ASSERT(sa->sa_len <= data.length());
|
||||
return addrToString(sa);
|
||||
}
|
||||
54
client/platforms/macos/daemon/macosroutemonitor.h
Normal file
54
client/platforms/macos/daemon/macosroutemonitor.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef MACOSROUTEMONITOR_H
|
||||
#define MACOSROUTEMONITOR_H
|
||||
|
||||
#include "ipaddressrange.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
struct if_msghdr;
|
||||
struct rt_msghdr;
|
||||
struct sockaddr;
|
||||
|
||||
class MacosRouteMonitor final : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr);
|
||||
~MacosRouteMonitor();
|
||||
|
||||
bool insertRoute(const IPAddressRange& prefix);
|
||||
bool deleteRoute(const IPAddressRange& prefix);
|
||||
int interfaceFlags() { return m_ifflags; }
|
||||
|
||||
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 handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
|
||||
bool rtmSendRoute(int action, const IPAddressRange& prefix);
|
||||
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
|
||||
const void* sa);
|
||||
static QList<QByteArray> parseAddrList(const QByteArray& data);
|
||||
|
||||
private slots:
|
||||
void rtsockReady();
|
||||
|
||||
private:
|
||||
static QString addrToString(const struct sockaddr* sa);
|
||||
static QString addrToString(const QByteArray& data);
|
||||
|
||||
QString m_ifname;
|
||||
int m_ifflags = 0;
|
||||
int m_rtsock = -1;
|
||||
int m_rtseq = 0;
|
||||
QSocketNotifier* m_notifier = nullptr;
|
||||
};
|
||||
|
||||
#endif // MACOSROUTEMONITOR_H
|
||||
309
client/platforms/macos/daemon/wireguardutilsmacos.cpp
Normal file
309
client/platforms/macos/daemon/wireguardutilsmacos.cpp
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
/* 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 "wireguardutilsmacos.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QLocalSocket>
|
||||
#include <QTimer>
|
||||
|
||||
#include <errno.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");
|
||||
}; // namespace
|
||||
|
||||
WireguardUtilsMacos::WireguardUtilsMacos(QObject* parent)
|
||||
: WireguardUtils(parent), m_tunnel(this) {
|
||||
MVPN_COUNT_CTOR(WireguardUtilsMacos);
|
||||
logger.debug() << "WireguardUtilsMacos created.";
|
||||
|
||||
connect(&m_tunnel, SIGNAL(readyReadStandardOutput()), this,
|
||||
SLOT(tunnelStdoutReady()));
|
||||
connect(&m_tunnel, SIGNAL(errorOccurred(QProcess::ProcessError)), this,
|
||||
SLOT(tunnelErrorOccurred(QProcess::ProcessError)));
|
||||
}
|
||||
|
||||
WireguardUtilsMacos::~WireguardUtilsMacos() {
|
||||
MVPN_COUNT_DTOR(WireguardUtilsMacos);
|
||||
logger.debug() << "WireguardUtilsMacos destroyed.";
|
||||
}
|
||||
|
||||
void WireguardUtilsMacos::tunnelStdoutReady() {
|
||||
for (;;) {
|
||||
QByteArray line = m_tunnel.readLine();
|
||||
if (line.length() <= 0) {
|
||||
break;
|
||||
}
|
||||
logwireguard.debug() << QString::fromUtf8(line);
|
||||
}
|
||||
}
|
||||
|
||||
void WireguardUtilsMacos::tunnelErrorOccurred(QProcess::ProcessError error) {
|
||||
logger.warning() << "Tunnel process encountered an error:" << error;
|
||||
emit backendFailure();
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
if (m_tunnel.state() != QProcess::NotRunning) {
|
||||
logger.warning() << "Unable to start: tunnel process already running";
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
if (!wgRuntimeDir.exists()) {
|
||||
wgRuntimeDir.mkpath(".");
|
||||
}
|
||||
|
||||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||
QString wgNameFile = wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name");
|
||||
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
|
||||
#ifdef QT_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)) {
|
||||
logger.error() << "Unable to start tunnel process due to timeout";
|
||||
m_tunnel.kill();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ifname = waitForTunnelName(wgNameFile);
|
||||
if (m_ifname.isNull()) {
|
||||
logger.error() << "Unable to read tunnel interface name";
|
||||
m_tunnel.kill();
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "Created wireguard interface" << m_ifname;
|
||||
|
||||
// Start the routing table monitor.
|
||||
m_rtmonitor = new MacosRouteMonitor(m_ifname, this);
|
||||
|
||||
// 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";
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||
}
|
||||
return (err == 0);
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deleteInterface() {
|
||||
if (m_rtmonitor) {
|
||||
delete m_rtmonitor;
|
||||
m_rtmonitor = nullptr;
|
||||
}
|
||||
|
||||
if (m_tunnel.state() == QProcess::NotRunning) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to terminate gracefully.
|
||||
m_tunnel.terminate();
|
||||
if (!m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT)) {
|
||||
m_tunnel.kill();
|
||||
m_tunnel.waitForFinished(WG_TUN_PROC_TIMEOUT);
|
||||
}
|
||||
|
||||
// Garbage collect.
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// dummy implementations for now
|
||||
bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||
QByteArray publicKey =
|
||||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||
|
||||
// Update/create the peer config
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||
if (!config.m_serverIpv4AddrIn.isNull()) {
|
||||
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
|
||||
} else if (!config.m_serverIpv6AddrIn.isNull()) {
|
||||
out << "endpoint=[" << config.m_serverIpv6AddrIn << "]:";
|
||||
} else {
|
||||
logger.warning() << "Failed to create peer with no endpoints";
|
||||
return false;
|
||||
}
|
||||
out << config.m_serverPort << "\n";
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
}
|
||||
return (err == 0);
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deletePeer(const QString& pubkey) {
|
||||
QByteArray publicKey = QByteArray::fromBase64(qPrintable(pubkey));
|
||||
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||
out << "remove=true\n";
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer deletion failed:" << strerror(err);
|
||||
}
|
||||
return (err == 0);
|
||||
}
|
||||
|
||||
WireguardUtils::peerStatus WireguardUtilsMacos::getPeerStatus(
|
||||
const QString& pubkey) {
|
||||
peerStatus status = {0, 0};
|
||||
QString hexkey = QByteArray::fromBase64(pubkey.toUtf8()).toHex();
|
||||
QString reply = uapiCommand("get=1");
|
||||
bool match = false;
|
||||
|
||||
for (const QString& line : reply.split('\n')) {
|
||||
int eq = line.indexOf('=');
|
||||
if (eq <= 0) {
|
||||
continue;
|
||||
}
|
||||
QString name = line.left(eq);
|
||||
QString value = line.mid(eq + 1);
|
||||
|
||||
if (name == "public_key") {
|
||||
match = (value == hexkey);
|
||||
continue;
|
||||
} else if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name == "tx_bytes") {
|
||||
status.txBytes = value.toDouble();
|
||||
}
|
||||
if (name == "rx_bytes") {
|
||||
status.rxBytes = value.toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::updateRoutePrefix(const IPAddressRange& prefix,
|
||||
int hopindex) {
|
||||
Q_UNUSED(hopindex);
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
return m_rtmonitor->insertRoute(prefix);
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddressRange& prefix,
|
||||
int hopindex) {
|
||||
Q_UNUSED(hopindex);
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
return m_rtmonitor->deleteRoute(prefix);
|
||||
}
|
||||
|
||||
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||
QLocalSocket socket;
|
||||
QTimer uapiTimeout;
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
QString wgSocketFile = wgRuntimeDir.filePath(m_ifname + ".sock");
|
||||
|
||||
uapiTimeout.setSingleShot(true);
|
||||
uapiTimeout.start(WG_TUN_PROC_TIMEOUT);
|
||||
|
||||
socket.connectToServer(wgSocketFile, QIODevice::ReadWrite);
|
||||
if (!socket.waitForConnected(WG_TUN_PROC_TIMEOUT)) {
|
||||
logger.error() << "QLocalSocket::waitForConnected() failed:"
|
||||
<< socket.errorString();
|
||||
return QString();
|
||||
}
|
||||
|
||||
// Send the message to the UAPI socket.
|
||||
QByteArray message = command.toLocal8Bit();
|
||||
while (!message.endsWith("\n\n")) {
|
||||
message.append('\n');
|
||||
}
|
||||
socket.write(message);
|
||||
|
||||
QByteArray reply;
|
||||
while (!reply.contains("\n\n")) {
|
||||
if (!uapiTimeout.isActive()) {
|
||||
logger.error() << "UAPI command timed out";
|
||||
return QString();
|
||||
}
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
reply.append(socket.readAll());
|
||||
}
|
||||
|
||||
return QString::fromUtf8(reply).trimmed();
|
||||
}
|
||||
|
||||
// static
|
||||
int WireguardUtilsMacos::uapiErrno(const QString& reply) {
|
||||
for (const QString& line : reply.split("\n")) {
|
||||
int eq = line.indexOf('=');
|
||||
if (eq <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (line.left(eq) == "errno") {
|
||||
return line.mid(eq + 1).toInt();
|
||||
}
|
||||
}
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
QString WireguardUtilsMacos::waitForTunnelName(const QString& filename) {
|
||||
QTimer timeout;
|
||||
timeout.setSingleShot(true);
|
||||
timeout.start(WG_TUN_PROC_TIMEOUT);
|
||||
|
||||
QFile file(filename);
|
||||
while ((m_tunnel.state() == QProcess::Running) && timeout.isActive()) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
continue;
|
||||
}
|
||||
QString ifname = QString::fromLocal8Bit(file.readLine()).trimmed();
|
||||
file.close();
|
||||
|
||||
// Test-connect to the UAPI socket.
|
||||
QLocalSocket sock;
|
||||
QDir wgRuntimeDir(WG_RUNTIME_DIR);
|
||||
QString sockName = wgRuntimeDir.filePath(ifname + ".sock");
|
||||
sock.connectToServer(sockName, QIODevice::ReadWrite);
|
||||
if (sock.waitForConnected(100)) {
|
||||
return ifname;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
52
client/platforms/macos/daemon/wireguardutilsmacos.h
Normal file
52
client/platforms/macos/daemon/wireguardutilsmacos.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* 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 WIREGUARDUTILSMACOS_H
|
||||
#define WIREGUARDUTILSMACOS_H
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "macosroutemonitor.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
|
||||
class WireguardUtilsMacos final : public WireguardUtils {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WireguardUtilsMacos(QObject* parent);
|
||||
~WireguardUtilsMacos();
|
||||
|
||||
bool interfaceExists() override {
|
||||
return m_tunnel.state() == QProcess::Running;
|
||||
}
|
||||
QString interfaceName() override { return m_ifname; }
|
||||
bool addInterface(const InterfaceConfig& config) override;
|
||||
bool deleteInterface() override;
|
||||
|
||||
bool updatePeer(const InterfaceConfig& config) override;
|
||||
bool deletePeer(const QString& pubkey) override;
|
||||
peerStatus getPeerStatus(const QString& pubkey) override;
|
||||
|
||||
bool updateRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
|
||||
bool deleteRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
|
||||
|
||||
signals:
|
||||
void backendFailure();
|
||||
|
||||
private slots:
|
||||
void tunnelStdoutReady();
|
||||
void tunnelErrorOccurred(QProcess::ProcessError error);
|
||||
|
||||
private:
|
||||
QString uapiCommand(const QString& command);
|
||||
static int uapiErrno(const QString& command);
|
||||
QString waitForTunnelName(const QString& filename);
|
||||
|
||||
QString m_ifname;
|
||||
QProcess m_tunnel;
|
||||
MacosRouteMonitor* m_rtmonitor = nullptr;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILSMACOS_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue