WireGuard rework for Linux

This commit is contained in:
Mykola Baibuz 2023-09-17 17:06:24 -04:00
parent f62076d3fd
commit 279692afea
30 changed files with 2319 additions and 36 deletions

View file

@ -0,0 +1,170 @@
/* 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 DBUSTYPESLINUX_H
#define DBUSTYPESLINUX_H
#include <sys/socket.h>
#include <QByteArray>
#include <QDBusArgument>
#include <QHostAddress>
#include <QtDBus/QtDBus>
/* D-Bus metatype for marshalling arguments to the SetLinkDNS method */
class DnsResolver : public QHostAddress {
public:
DnsResolver(const QHostAddress& address = QHostAddress())
: QHostAddress(address) {}
friend QDBusArgument& operator<<(QDBusArgument& args, const DnsResolver& ip) {
args.beginStructure();
if (ip.protocol() == QAbstractSocket::IPv6Protocol) {
Q_IPV6ADDR addrv6 = ip.toIPv6Address();
args << AF_INET6;
args << QByteArray::fromRawData((const char*)&addrv6, sizeof(addrv6));
} else {
quint32 addrv4 = ip.toIPv4Address();
QByteArray data(4, 0);
data[0] = (addrv4 >> 24) & 0xff;
data[1] = (addrv4 >> 16) & 0xff;
data[2] = (addrv4 >> 8) & 0xff;
data[3] = (addrv4 >> 0) & 0xff;
args << AF_INET;
args << data;
}
args.endStructure();
return args;
}
friend const QDBusArgument& operator>>(const QDBusArgument& args,
DnsResolver& ip) {
int family;
QByteArray data;
args.beginStructure();
args >> family >> data;
args.endStructure();
if (family == AF_INET6) {
ip.setAddress(data.constData());
} else if (data.count() >= 4) {
quint32 addrv4 = 0;
addrv4 |= (data[0] << 24);
addrv4 |= (data[1] << 16);
addrv4 |= (data[2] << 8);
addrv4 |= (data[3] << 0);
ip.setAddress(addrv4);
}
return args;
}
};
typedef QList<DnsResolver> DnsResolverList;
Q_DECLARE_METATYPE(DnsResolver);
Q_DECLARE_METATYPE(DnsResolverList);
/* D-Bus metatype for marshalling arguments to the SetLinkDomains method */
class DnsLinkDomain {
public:
DnsLinkDomain(const QString d = "", bool s = false) {
domain = d;
search = s;
};
QString domain;
bool search;
friend QDBusArgument& operator<<(QDBusArgument& args,
const DnsLinkDomain& data) {
args.beginStructure();
args << data.domain << data.search;
args.endStructure();
return args;
}
friend const QDBusArgument& operator>>(const QDBusArgument& args,
DnsLinkDomain& data) {
args.beginStructure();
args >> data.domain >> data.search;
args.endStructure();
return args;
}
bool operator==(const DnsLinkDomain& other) const {
return (domain == other.domain) && (search == other.search);
}
bool operator==(const QString& other) const { return (domain == other); }
};
typedef QList<DnsLinkDomain> DnsLinkDomainList;
Q_DECLARE_METATYPE(DnsLinkDomain);
Q_DECLARE_METATYPE(DnsLinkDomainList);
/* D-Bus metatype for marshalling the Domains property */
class DnsDomain {
public:
DnsDomain() {}
int ifindex = 0;
QString domain = "";
bool search = false;
friend QDBusArgument& operator<<(QDBusArgument& args, const DnsDomain& data) {
args.beginStructure();
args << data.ifindex << data.domain << data.search;
args.endStructure();
return args;
}
friend const QDBusArgument& operator>>(const QDBusArgument& args,
DnsDomain& data) {
args.beginStructure();
args >> data.ifindex >> data.domain >> data.search;
args.endStructure();
return args;
}
};
typedef QList<DnsDomain> DnsDomainList;
Q_DECLARE_METATYPE(DnsDomain);
Q_DECLARE_METATYPE(DnsDomainList);
/* D-Bus metatype for marshalling the freedesktop login manager data. */
class UserData {
public:
QString name;
uint userid;
QDBusObjectPath path;
friend QDBusArgument& operator<<(QDBusArgument& args, const UserData& data) {
args.beginStructure();
args << data.userid << data.name << data.path;
args.endStructure();
return args;
}
friend const QDBusArgument& operator>>(const QDBusArgument& args,
UserData& data) {
args.beginStructure();
args >> data.userid >> data.name >> data.path;
args.endStructure();
return args;
}
};
typedef QList<UserData> UserDataList;
Q_DECLARE_METATYPE(UserData);
Q_DECLARE_METATYPE(UserDataList);
class DnsMetatypeRegistrationProxy {
public:
DnsMetatypeRegistrationProxy() {
qRegisterMetaType<DnsResolver>();
qDBusRegisterMetaType<DnsResolver>();
qRegisterMetaType<DnsResolverList>();
qDBusRegisterMetaType<DnsResolverList>();
qRegisterMetaType<DnsLinkDomain>();
qDBusRegisterMetaType<DnsLinkDomain>();
qRegisterMetaType<DnsLinkDomainList>();
qDBusRegisterMetaType<DnsLinkDomainList>();
qRegisterMetaType<DnsDomain>();
qDBusRegisterMetaType<DnsDomain>();
qRegisterMetaType<DnsDomainList>();
qDBusRegisterMetaType<DnsDomainList>();
qRegisterMetaType<UserData>();
qDBusRegisterMetaType<UserData>();
qRegisterMetaType<UserDataList>();
qDBusRegisterMetaType<UserDataList>();
}
};
#endif // DBUSTYPESLINUX_H

View file

@ -0,0 +1,212 @@
/* 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 "dnsutilslinux.h"
#include <net/if.h>
#include <QDBusVariant>
#include <QtDBus/QtDBus>
#include "leakdetector.h"
#include "logger.h"
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
constexpr const char* DBUS_RESOLVE_MANAGER = "org.freedesktop.resolve1.Manager";
constexpr const char* DBUS_PROPERTY_INTERFACE =
"org.freedesktop.DBus.Properties";
namespace {
Logger logger("DnsUtilsLinux");
}
DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
MZ_COUNT_CTOR(DnsUtilsLinux);
logger.debug() << "DnsUtilsLinux created.";
QDBusConnection conn = QDBusConnection::systemBus();
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER, conn, this);
}
DnsUtilsLinux::~DnsUtilsLinux() {
MZ_COUNT_DTOR(DnsUtilsLinux);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_ifindex > 0) {
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
}
logger.debug() << "DnsUtilsLinux destroyed.";
}
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) {
m_ifindex = if_nametoindex(qPrintable(ifname));
if (m_ifindex <= 0) {
logger.error() << "Unable to resolve ifindex for" << ifname;
return false;
}
setLinkDNS(m_ifindex, resolvers);
setLinkDefaultRoute(m_ifindex, true);
updateLinkDomains();
return true;
}
bool DnsUtilsLinux::restoreResolvers() {
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
setLinkDomains(iterator.key(), iterator.value());
}
m_linkDomains.clear();
/* Revert the VPN interface's DNS configuration */
if (m_ifindex > 0) {
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("RevertLink"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
m_ifindex = 0;
}
return true;
}
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
}
delete call;
}
void DnsUtilsLinux::setLinkDNS(int ifindex,
const QList<QHostAddress>& resolvers) {
QList<DnsResolver> resolverList;
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
for (const auto& ip : resolvers) {
resolverList.append(ip);
if (ifname) {
logger.debug() << "Adding DNS resolver" << ip.toString() << "via"
<< ifname;
}
}
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(resolverList);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDNS"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::setLinkDomains(int ifindex,
const QList<DnsLinkDomain>& domains) {
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
if (ifname) {
for (const auto& d : domains) {
// The DNS search domains often winds up revealing user's ISP which
// can correlate back to their location.
logger.debug() << "Setting DNS domain:" << logger.sensitive(d.domain)
<< "via" << ifname << (d.search ? "search" : "");
}
}
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(domains);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDomains"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(enable);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDefaultRoute"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::updateLinkDomains() {
/* Get the list of search domains, and remove any others that might conspire
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
* seem to be able to demarshall complex property types.
*/
QDBusMessage message = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_PROPERTY_INTERFACE, "Get");
message << QString(DBUS_RESOLVE_MANAGER);
message << QString("Domains");
QDBusPendingReply<QVariant> reply =
m_resolver->connection().asyncCall(message);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsDomainsReceived(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QVariant> reply = *call;
if (reply.isError()) {
logger.error() << "Error retrieving the DNS domains from the DBus service";
delete call;
return;
}
/* Update the state of the DNS domains */
m_linkDomains.clear();
QDBusArgument args = qvariant_cast<QDBusArgument>(reply.value());
QList<DnsDomain> list = qdbus_cast<QList<DnsDomain>>(args);
for (const auto& d : list) {
if (d.ifindex == 0) {
continue;
}
m_linkDomains[d.ifindex].append(DnsLinkDomain(d.domain, d.search));
}
/* Drop any competing root search domains. */
DnsLinkDomain root = DnsLinkDomain(".", true);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
if (!iterator.value().contains(root)) {
continue;
}
QList<DnsLinkDomain> newlist = iterator.value();
newlist.removeAll(root);
setLinkDomains(iterator.key(), newlist);
}
/* Add a root search domain for the new interface. */
QList<DnsLinkDomain> newlist = {root};
setLinkDomains(m_ifindex, newlist);
delete call;
}
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;

View file

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DNSUTILSLINUX_H
#define DNSUTILSLINUX_H
#include <QDBusInterface>
#include <QDBusPendingCallWatcher>
#include "daemon/dnsutils.h"
#include "dbustypeslinux.h"
class DnsUtilsLinux final : public DnsUtils {
Q_OBJECT
Q_DISABLE_COPY_MOVE(DnsUtilsLinux)
public:
DnsUtilsLinux(QObject* parent);
~DnsUtilsLinux();
bool updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) override;
bool restoreResolvers() override;
private:
void setLinkDNS(int ifindex, const QList<QHostAddress>& resolvers);
void setLinkDomains(int ifindex, const QList<DnsLinkDomain>& domains);
void setLinkDefaultRoute(int ifindex, bool enable);
void updateLinkDomains();
private slots:
void dnsCallCompleted(QDBusPendingCallWatcher*);
void dnsDomainsReceived(QDBusPendingCallWatcher*);
private:
int m_ifindex = 0;
QMap<int, DnsLinkDomainList> m_linkDomains;
QDBusInterface* m_resolver = nullptr;
};
#endif // DNSUTILSLINUX_H

View file

@ -0,0 +1,150 @@
/* 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 "iputilslinux.h"
#include <arpa/inet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <QHostAddress>
#include <QScopeGuard>
#include "daemon/wireguardutils.h"
#include "leakdetector.h"
#include "logger.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace {
Logger logger("IPUtilsLinux");
}
IPUtilsLinux::IPUtilsLinux(QObject* parent) : IPUtils(parent) {
MZ_COUNT_CTOR(IPUtilsLinux);
logger.debug() << "IPUtilsLinux created.";
}
IPUtilsLinux::~IPUtilsLinux() {
MZ_COUNT_DTOR(IPUtilsLinux);
logger.debug() << "IPUtilsLinux destroyed.";
}
bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
}
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
Q_UNUSED(config);
// 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); });
// Setup the interface to interact with
struct ifreq ifr;
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
// MTU
// FIXME: We need to know how many layers deep this particular
// interface is into a tunnel to work effectively. Otherwise
// we will run into fragmentation issues.
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
if (ret) {
logger.error() << "Failed to set MTU -- Return code: " << ret;
return false;
}
// Up
ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
ret = ioctl(sockfd, SIOCSIFFLAGS, &ifr);
if (ret) {
logger.error() << "Failed to set device up -- Return code: " << ret;
return false;
}
return true;
}
bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
struct ifreq ifr;
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
// Name the interface and set family
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
ifr.ifr_addr.sa_family = AF_INET;
// 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();
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
// Create 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, SIOCSIFADDR, &ifr);
if (ret) {
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
<< "error:" << strerror(errno);
return false;
}
return true;
}
bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
// Set up the ifr and the companion ifr6
struct in6_ifreq ifr6;
ifr6.prefixlen = 64;
// Get the device address to add to ifr6 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.addr);
// Create IPv6 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); });
// Get the index of named ifr and link with ifr6
struct ifreq ifr;
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
ifr.ifr_addr.sa_family = AF_INET6;
int ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
if (ret) {
logger.error() << "Failed to get ifindex. Return code: " << ret;
return false;
}
ifr6.ifindex = ifr.ifr_ifindex;
// Set ifr6 to the interface
ret = ioctl(sockfd, SIOCSIFADDR, &ifr6);
if (ret && (errno != EEXIST)) {
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
<< "error:" << strerror(errno);
return false;
}
return true;
}

View file

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IPUTILSLINUX_H
#define IPUTILSLINUX_H
#include <arpa/inet.h>
#include "daemon/iputils.h"
class IPUtilsLinux final : public IPUtils {
public:
IPUtilsLinux(QObject* parent);
~IPUtilsLinux();
bool addInterfaceIPs(const InterfaceConfig& config) override;
bool setMTUAndUp(const InterfaceConfig& config) override;
private:
bool addIP4AddressToDevice(const InterfaceConfig& config);
bool addIP6AddressToDevice(const InterfaceConfig& config);
private:
struct in6_ifreq {
struct in6_addr addr;
uint32_t prefixlen;
unsigned int ifindex;
};
};
#endif // IPUTILSLINUX_H

View 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/. */
#include "linuxdaemon.h"
#include <QCoreApplication>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QLocalSocket>
#include <QProcess>
#include <QSettings>
#include <QTextStream>
#include <QtGlobal>
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger("LinuxDaemon");
LinuxDaemon* s_daemon = nullptr;
} // namespace
LinuxDaemon::LinuxDaemon() : Daemon(nullptr) {
MZ_COUNT_CTOR(LinuxDaemon);
logger.debug() << "Daemon created";
m_wgutils = new WireguardUtilsLinux(this);
m_dnsutils = new DnsUtilsLinux(this);
m_iputils = new IPUtilsLinux(this);
Q_ASSERT(s_daemon == nullptr);
s_daemon = this;
}
LinuxDaemon::~LinuxDaemon() {
MZ_COUNT_DTOR(LinuxDaemon);
logger.debug() << "Daemon released";
Q_ASSERT(s_daemon == this);
s_daemon = nullptr;
}
// static
LinuxDaemon* LinuxDaemon::instance() {
Q_ASSERT(s_daemon);
return s_daemon;
}

View file

@ -0,0 +1,36 @@
/* 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 LINUXDAEMON_H
#define LINUXDAEMON_H
#include "daemon/daemon.h"
#include "dnsutilslinux.h"
#include "iputilslinux.h"
#include "wireguardutilslinux.h"
class LinuxDaemon final : public Daemon {
friend class IPUtilsMacos;
public:
LinuxDaemon();
~LinuxDaemon();
static LinuxDaemon* instance();
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:
WireguardUtilsLinux* m_wgutils = nullptr;
DnsUtilsLinux* m_dnsutils = nullptr;
IPUtilsLinux* m_iputils = nullptr;
};
#endif // LINUXDAEMON_H

View file

@ -0,0 +1,133 @@
/* 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 "linuxroutemonitor.h"
#include "router_linux.h"
#include <arpa/inet.h>
#include <errno.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <QNetworkInterface>
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/socket.h>
#include <unistd.h>
#include <QCoreApplication>
#include <QProcess>
#include <QScopeGuard>
#include <QTimer>
#include "leakdetector.h"
#include "logger.h"
namespace {
Logger logger("LinuxRouteMonitor");
} // namespace
LinuxRouteMonitor::LinuxRouteMonitor(const QString& ifname, QObject* parent)
: QObject(parent), m_ifname(ifname) {
MZ_COUNT_CTOR(LinuxRouteMonitor);
logger.debug() << "LinuxRouteMonitor created.";
m_rtsock = socket(PF_ROUTE, SOCK_RAW, 0);
if (m_rtsock < 0) {
logger.error() << "Failed to create routing socket:" << strerror(errno);
return;
}
RouterLinux &router = RouterLinux::Instance();
m_defaultGatewayIpv4 = router.getgatewayandiface().toUtf8();
m_ifindex = if_nametoindex(qPrintable(ifname));
m_notifier = new QSocketNotifier(m_rtsock, QSocketNotifier::Read, this);
}
LinuxRouteMonitor::~LinuxRouteMonitor() {
MZ_COUNT_DTOR(LinuxRouteMonitor);
flushExclusionRoutes();
if (m_rtsock >= 0) {
close(m_rtsock);
}
logger.debug() << "LinuxRouteMonitor destroyed.";
}
// 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;
}
return 0;
}
bool LinuxRouteMonitor::insertRoute(const IPAddress& prefix) {
int temp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
struct ifreq ifc;
int res;
if(temp_sock < 0)
return -1;
strcpy(ifc.ifr_name, m_ifname.toUtf8());
res = ioctl(temp_sock, SIOCGIFADDR, &ifc);
if(res < 0)
return -1;
RouterLinux &router = RouterLinux::Instance();
logger.debug() << "prefix.toString() " << prefix.toString() << " m_ifname " << inet_ntoa(((struct sockaddr_in*)&ifc.ifr_addr)->sin_addr);
return router.routeAdd(prefix.toString(), inet_ntoa(((struct sockaddr_in*)&ifc.ifr_addr)->sin_addr), temp_sock);
}
bool LinuxRouteMonitor::deleteRoute(const IPAddress& prefix) {
int temp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
struct ifreq ifc;
int res;
if(temp_sock < 0)
temp_sock -1;
strcpy(ifc.ifr_name, m_ifname.toUtf8());
res = ioctl(temp_sock, SIOCGIFADDR, &ifc);
if(res < 0)
return -1;
RouterLinux &router = RouterLinux::Instance();
logger.debug() << "prefix.toString() " << prefix.toString() << " m_ifname " << inet_ntoa(((struct sockaddr_in*)&ifc.ifr_addr)->sin_addr);
return router.routeDelete(prefix.toString(), inet_ntoa(((struct sockaddr_in*)&ifc.ifr_addr)->sin_addr), temp_sock);
}
bool LinuxRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Adding exclusion route for"
<< logger.sensitive(prefix.toString());
int temp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
RouterLinux &router = RouterLinux::Instance();
logger.debug() << "prefix.toString() " << prefix.toString() << " m_defaultGatewayIpv4 " << m_defaultGatewayIpv4;
return router.routeAdd(prefix.toString(), m_defaultGatewayIpv4, temp_sock);
// Otherwise, the default route isn't known yet. Do nothing.
return true;
}
bool LinuxRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Deleting exclusion route for"
<< logger.sensitive(prefix.toString());
int temp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
RouterLinux &router = RouterLinux::Instance();
logger.debug() << "prefix.toString() " << prefix.toString() << " m_defaultGatewayIpv4 " << m_defaultGatewayIpv4;
return router.routeDelete(prefix.toString(), m_defaultGatewayIpv4, temp_sock);
}
void LinuxRouteMonitor::flushExclusionRoutes() {
RouterLinux &router = RouterLinux::Instance();
router.clearSavedRoutes();
}

View file

@ -0,0 +1,53 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef LINUXROUTEMONITOR_H
#define LINUXROUTEMONITOR_H
#include <QByteArray>
#include <QHostAddress>
#include <QList>
#include <QObject>
#include <QSocketNotifier>
#include "ipaddress.h"
struct if_msghdr;
struct rt_msghdr;
struct sockaddr;
class LinuxRouteMonitor final : public QObject {
Q_OBJECT
public:
LinuxRouteMonitor(const QString& ifname, QObject* parent = nullptr);
~LinuxRouteMonitor();
bool insertRoute(const IPAddress& prefix);
bool deleteRoute(const IPAddress& prefix);
int interfaceFlags() { return m_ifflags; }
bool addExclusionRoute(const IPAddress& prefix);
bool deleteExclusionRoute(const IPAddress& prefix);
void flushExclusionRoutes();
private:
static QString addrToString(const struct sockaddr* sa);
static QString addrToString(const QByteArray& data);
QList<IPAddress> 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;
QSocketNotifier* m_notifier = nullptr;
};
#endif // LINUXROUTEMONITOR_H

View file

@ -0,0 +1,228 @@
/* 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 "pidtracker.h"
#include <errno.h>
#include <limits.h>
#include <linux/cn_proc.h>
#include <linux/connector.h>
#include <linux/netlink.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <unistd.h>
#include "leakdetector.h"
#include "logger.h"
constexpr size_t CN_MCAST_MSG_SIZE =
sizeof(struct cn_msg) + sizeof(enum proc_cn_mcast_op);
namespace {
Logger logger("PidTracker");
}
PidTracker::PidTracker(QObject* parent) : QObject(parent) {
MZ_COUNT_CTOR(PidTracker);
logger.debug() << "PidTracker created.";
m_nlsock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
if (m_nlsock < 0) {
logger.error() << "Failed to create netlink socket:" << strerror(errno);
return;
}
struct sockaddr_nl nladdr;
nladdr.nl_family = AF_NETLINK;
nladdr.nl_groups = CN_IDX_PROC;
nladdr.nl_pid = getpid();
nladdr.nl_pad = 0;
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) {
logger.error() << "Failed to bind netlink socket:" << strerror(errno);
close(m_nlsock);
m_nlsock = -1;
return;
}
char buf[NLMSG_SPACE(CN_MCAST_MSG_SIZE)];
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
struct cn_msg* cnmsg = (struct cn_msg*)NLMSG_DATA(nlmsg);
enum proc_cn_mcast_op mcast_op = PROC_CN_MCAST_LISTEN;
memset(buf, 0, sizeof(buf));
nlmsg->nlmsg_len = NLMSG_LENGTH(CN_MCAST_MSG_SIZE);
nlmsg->nlmsg_type = NLMSG_DONE;
nlmsg->nlmsg_flags = 0;
nlmsg->nlmsg_seq = 0;
nlmsg->nlmsg_pid = getpid();
cnmsg->id.idx = CN_IDX_PROC;
cnmsg->id.val = CN_VAL_PROC;
cnmsg->seq = 0;
cnmsg->ack = 0;
cnmsg->len = sizeof(mcast_op);
memcpy(cnmsg->data, &mcast_op, sizeof(mcast_op));
if (send(m_nlsock, nlmsg, sizeof(buf), 0) != sizeof(buf)) {
logger.error() << "Failed to send netlink message:" << strerror(errno);
close(m_nlsock);
m_nlsock = -1;
return;
}
m_socket = new QSocketNotifier(m_nlsock, QSocketNotifier::Read, this);
connect(m_socket, &QSocketNotifier::activated, this, &PidTracker::readData);
}
PidTracker::~PidTracker() {
MZ_COUNT_DTOR(PidTracker);
logger.debug() << "PidTracker destroyed.";
m_processTree.clear();
while (!m_processGroups.isEmpty()) {
ProcessGroup* group = m_processGroups.takeFirst();
delete group;
}
if (m_nlsock > 0) {
close(m_nlsock);
}
}
ProcessGroup* PidTracker::track(const QString& name, int rootpid) {
ProcessGroup* group = m_processTree.value(rootpid, nullptr);
if (group) {
logger.warning() << "Ignoring attempt to track duplicate PID";
return group;
}
group = new ProcessGroup(name, rootpid);
group->kthreads[rootpid] = 1;
group->refcount = 1;
m_processGroups.append(group);
m_processTree[rootpid] = group;
return group;
}
void PidTracker::handleProcEvent(struct cn_msg* cnmsg) {
struct proc_event* ev = (struct proc_event*)cnmsg->data;
if (ev->what == proc_event::PROC_EVENT_FORK) {
auto forkdata = &ev->event_data.fork;
/* If the child process already exists, track a new kernel thread. */
ProcessGroup* group = m_processTree.value(forkdata->child_tgid, nullptr);
if (group) {
group->kthreads[forkdata->child_tgid]++;
return;
}
/* Track a new userspace process if was forked from a known parent. */
group = m_processTree.value(forkdata->parent_tgid, nullptr);
if (!group) {
return;
}
m_processTree[forkdata->child_tgid] = group;
group->kthreads[forkdata->child_tgid] = 1;
group->refcount++;
emit pidForked(group->name, forkdata->parent_tgid, forkdata->child_tgid);
}
if (ev->what == proc_event::PROC_EVENT_EXIT) {
auto exitdata = &ev->event_data.exit;
ProcessGroup* group = m_processTree.value(exitdata->process_tgid, nullptr);
if (!group) {
return;
}
/* Decrement the number of kernel threads in this userspace process. */
uint threadcount = group->kthreads.value(exitdata->process_tgid, 0);
if (threadcount == 0) {
return;
}
if (threadcount > 1) {
group->kthreads[exitdata->process_tgid] = threadcount - 1;
return;
}
group->kthreads.remove(exitdata->process_tgid);
/* A userspace process exits when all of its kernel threads exit. */
Q_ASSERT(group->refcount > 0);
group->refcount--;
if (group->refcount == 0) {
emit terminated(group->name, group->rootpid);
m_processGroups.removeAll(group);
delete group;
}
}
}
void PidTracker::readData() {
struct sockaddr_nl src;
socklen_t srclen = sizeof(src);
ssize_t recvlen;
recvlen = recvfrom(m_nlsock, m_readBuf, sizeof(m_readBuf), MSG_DONTWAIT,
(struct sockaddr*)&src, &srclen);
if (recvlen == ENOBUFS) {
logger.error()
<< "Failed to read netlink socket: buffer full, message dropped";
return;
}
if (recvlen < 0) {
logger.error() << "Failed to read netlink socket:" << strerror(errno);
return;
}
if (srclen != sizeof(src)) {
logger.error() << "Failed to read netlink socket: invalid address length";
return;
}
/* We are only interested in process-control messages from the kernel */
if ((src.nl_groups != CN_IDX_PROC) || (src.nl_pid != 0)) {
return;
}
/* Handle the process-control messages. */
struct nlmsghdr* msg;
for (msg = (struct nlmsghdr*)m_readBuf; NLMSG_OK(msg, recvlen);
msg = NLMSG_NEXT(msg, recvlen)) {
struct cn_msg* cnmsg = (struct cn_msg*)NLMSG_DATA(msg);
if (msg->nlmsg_type == NLMSG_NOOP) {
continue;
}
if ((msg->nlmsg_type == NLMSG_ERROR) ||
(msg->nlmsg_type == NLMSG_OVERRUN)) {
break;
}
handleProcEvent(cnmsg);
if (msg->nlmsg_type == NLMSG_DONE) {
break;
}
}
}
bool ProcessGroup::moveToCgroup(const QString& name) {
/* Do nothing if Cgroups are not supported. */
if (name.isNull()) {
return true;
}
QString cgProcsFile = name + "/cgroup.procs";
FILE* fp = fopen(qPrintable(cgProcsFile), "w");
if (!fp) {
return false;
}
for (auto iterator = kthreads.constBegin(); iterator != kthreads.constEnd();
++iterator) {
fprintf(fp, "%d\n", iterator.key());
fflush(fp);
}
fclose(fp);
return true;
}

View file

@ -0,0 +1,72 @@
/* 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 PIDTRACKER_H
#define PIDTRACKER_H
#include <QHash>
#include <QList>
#include <QSocketNotifier>
#include <QString>
#include "leakdetector.h"
struct cn_msg;
class ProcessGroup {
public:
ProcessGroup(const QString& groupName, int groupRootPid,
const QString& groupState = "active") {
MZ_COUNT_CTOR(ProcessGroup);
name = groupName;
rootpid = groupRootPid;
state = groupState;
refcount = 0;
}
~ProcessGroup() { MZ_COUNT_DTOR(ProcessGroup); }
bool moveToCgroup(const QString& name);
QHash<int, uint> kthreads;
QString name;
QString state;
int rootpid;
int refcount;
};
class PidTracker final : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(PidTracker)
public:
explicit PidTracker(QObject* parent);
~PidTracker();
ProcessGroup* track(const QString& name, int rootpid);
QList<int> pids() { return m_processTree.keys(); }
QList<ProcessGroup*>::iterator begin() { return m_processGroups.begin(); }
QList<ProcessGroup*>::iterator end() { return m_processGroups.end(); }
ProcessGroup* group(int pid) { return m_processTree.value(pid); }
signals:
void pidForked(const QString& name, int parent, int child);
void pidExited(const QString& name, int pid);
void terminated(const QString& name, int rootpid);
private:
void handleProcEvent(struct cn_msg*);
private slots:
void readData();
private:
int m_nlsock;
char m_readBuf[2048];
QSocketNotifier* m_socket = nullptr;
QHash<int, ProcessGroup*> m_processTree;
QList<ProcessGroup*> m_processGroups;
};
#endif // PIDTRACKER_H

View file

@ -0,0 +1,369 @@
/* 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 "wireguardutilslinux.h"
#include <errno.h>
#include <QByteArray>
#include <QDir>
#include <QFile>
#include <QLocalSocket>
#include <QTimer>
#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("WireguardUtilsLinux");
Logger logwireguard("WireguardGo");
}; // namespace
WireguardUtilsLinux::WireguardUtilsLinux(QObject* parent)
: WireguardUtils(parent), m_tunnel(this) {
MZ_COUNT_CTOR(WireguardUtilsLinux);
logger.debug() << "WireguardUtilsLinux created.";
connect(&m_tunnel, SIGNAL(readyReadStandardOutput()), this,
SLOT(tunnelStdoutReady()));
connect(&m_tunnel, SIGNAL(errorOccurred(QProcess::ProcessError)), this,
SLOT(tunnelErrorOccurred(QProcess::ProcessError)));
}
WireguardUtilsLinux::~WireguardUtilsLinux() {
MZ_COUNT_DTOR(WireguardUtilsLinux);
logger.debug() << "WireguardUtilsLinux destroyed.";
}
void WireguardUtilsLinux::tunnelStdoutReady() {
for (;;) {
QByteArray line = m_tunnel.readLine();
if (line.length() <= 0) {
break;
}
logwireguard.debug() << QString::fromUtf8(line);
}
}
void WireguardUtilsLinux::tunnelErrorOccurred(QProcess::ProcessError error) {
logger.warning() << "Tunnel process encountered an error:" << error;
emit backendFailure();
}
bool WireguardUtilsLinux::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) + ".sock");
pe.insert("WG_TUN_NAME_FILE", wgNameFile);
#ifdef MZ_DEBUG
pe.insert("LOG_LEVEL", "debug");
#endif
m_tunnel.setProcessEnvironment(pe);
QDir appPath(QCoreApplication::applicationDirPath());
QStringList wgArgs = {"-f", "amn0"};
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 LinuxRouteMonitor(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 WireguardUtilsLinux::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 WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
QByteArray pskKey = QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
logger.debug() << "Configuring peer" << config.m_serverPublicKey << "via" << config.m_serverIpv4AddrIn;
// 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()) {
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";
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
out << "allowed_ip=" << ip.toString() << "\n";
}
// Exclude the server address, except for multihop exit servers.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
int err = uapiErrno(uapiCommand(message));
if (err != 0) {
logger.error() << "Peer configuration failed:" << strerror(err);
}
return (err == 0);
}
bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) {
QByteArray publicKey =
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
// Clear exclustion routes for this peer.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
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);
}
QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
QString reply = uapiCommand("get=1");
PeerStatus status;
QList<PeerStatus> peerList;
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") {
if (!status.m_pubkey.isEmpty()) {
peerList.append(status);
}
QByteArray pubkey = QByteArray::fromHex(value.toUtf8());
status = PeerStatus(pubkey.toBase64());
}
if (name == "tx_bytes") {
status.m_txBytes = value.toDouble();
}
if (name == "rx_bytes") {
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 peerList;
}
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
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 WireguardUtilsLinux::deleteRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
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 WireguardUtilsLinux::addExclusionRoute(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
return m_rtmonitor->addExclusionRoute(prefix);
}
bool WireguardUtilsLinux::deleteExclusionRoute(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
return m_rtmonitor->deleteExclusionRoute(prefix);
}
QString WireguardUtilsLinux::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 WireguardUtilsLinux::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 WireguardUtilsLinux::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);
QString ifname = "amn0";
// 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();
}

View file

@ -0,0 +1,55 @@
/* 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 WIREGUARDUTILSLINUX_H
#define WIREGUARDUTILSLINUX_H
#include <QObject>
#include <QProcess>
#include "daemon/wireguardutils.h"
#include "linuxroutemonitor.h"
class WireguardUtilsLinux final : public WireguardUtils {
Q_OBJECT
public:
WireguardUtilsLinux(QObject* parent);
~WireguardUtilsLinux();
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 InterfaceConfig& config) override;
QList<PeerStatus> getPeerStatus() override;
bool updateRoutePrefix(const IPAddress& prefix) override;
bool deleteRoutePrefix(const IPAddress& prefix) override;
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) 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;
LinuxRouteMonitor* m_rtmonitor = nullptr;
};
#endif // WIREGUARDUTILSLINUX_H