WireGuard rework for Linux
This commit is contained in:
parent
f62076d3fd
commit
279692afea
30 changed files with 2319 additions and 36 deletions
|
@ -12,7 +12,7 @@
|
|||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#ifdef MZ_MACOS
|
||||
#if defined(MZ_MACOS) || defined(MZ_LINUX)
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
|
@ -68,7 +68,8 @@ bool DaemonLocalServer::initialize() {
|
|||
QString DaemonLocalServer::daemonPath() const {
|
||||
#if defined(MZ_WINDOWS)
|
||||
return "\\\\.\\pipe\\amneziavpn";
|
||||
#elif defined(MZ_MACOS)
|
||||
#endif
|
||||
#if defined(MZ_MACOS) || defined(MZ_LINUX)
|
||||
QDir dir("/var/run");
|
||||
if (!dir.exists()) {
|
||||
logger.warning() << "/var/run doesn't exist. Fallback /tmp.";
|
||||
|
@ -76,12 +77,12 @@ QString DaemonLocalServer::daemonPath() const {
|
|||
}
|
||||
|
||||
if (dir.exists("amneziavpn")) {
|
||||
logger.debug() << "/var/run/amneziavpn seems to be usable";
|
||||
logger.debug() << "/var/run/amnezia seems to be usable";
|
||||
return VAR_PATH;
|
||||
}
|
||||
|
||||
if (!dir.mkdir("amneziavpn")) {
|
||||
logger.warning() << "Failed to create /var/run/amneziavpn";
|
||||
logger.warning() << "Failed to create /var/run/amnezia";
|
||||
return TMP_PATH;
|
||||
}
|
||||
|
||||
|
@ -92,7 +93,5 @@ QString DaemonLocalServer::daemonPath() const {
|
|||
}
|
||||
|
||||
return VAR_PATH;
|
||||
#else
|
||||
# error Unsupported platform
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#endif
|
||||
|
||||
#ifdef MZ_LINUX
|
||||
//# include "platforms/linux/linuxnetworkwatcher.h"
|
||||
# include "platforms/linux/linuxnetworkwatcher.h"
|
||||
#endif
|
||||
|
||||
#ifdef MZ_MACOS
|
||||
|
@ -56,7 +56,7 @@ void NetworkWatcher::initialize() {
|
|||
#if defined(MZ_WINDOWS)
|
||||
m_impl = new WindowsNetworkWatcher(this);
|
||||
#elif defined(MZ_LINUX)
|
||||
// m_impl = new LinuxNetworkWatcher(this);
|
||||
m_impl = new LinuxNetworkWatcher(this);
|
||||
#elif defined(MZ_MACOS)
|
||||
m_impl = new MacOSNetworkWatcher(this);
|
||||
#elif defined(MZ_WASM)
|
||||
|
|
|
@ -30,7 +30,7 @@ IPAddress::IPAddress(const QString& ip) {
|
|||
m_prefixLength = 128;
|
||||
}
|
||||
} else {
|
||||
Q_ASSERT(false);
|
||||
// Q_ASSERT(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
170
client/platforms/linux/daemon/dbustypeslinux.h
Normal file
170
client/platforms/linux/daemon/dbustypeslinux.h
Normal 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
|
212
client/platforms/linux/daemon/dnsutilslinux.cpp
Normal file
212
client/platforms/linux/daemon/dnsutilslinux.cpp
Normal 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;
|
41
client/platforms/linux/daemon/dnsutilslinux.h
Normal file
41
client/platforms/linux/daemon/dnsutilslinux.h
Normal 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
|
150
client/platforms/linux/daemon/iputilslinux.cpp
Normal file
150
client/platforms/linux/daemon/iputilslinux.cpp
Normal 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;
|
||||
}
|
31
client/platforms/linux/daemon/iputilslinux.h
Normal file
31
client/platforms/linux/daemon/iputilslinux.h
Normal 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
|
52
client/platforms/linux/daemon/linuxdaemon.cpp
Normal file
52
client/platforms/linux/daemon/linuxdaemon.cpp
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/. */
|
||||
|
||||
#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;
|
||||
}
|
36
client/platforms/linux/daemon/linuxdaemon.h
Normal file
36
client/platforms/linux/daemon/linuxdaemon.h
Normal 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
|
133
client/platforms/linux/daemon/linuxroutemonitor.cpp
Normal file
133
client/platforms/linux/daemon/linuxroutemonitor.cpp
Normal 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();
|
||||
}
|
53
client/platforms/linux/daemon/linuxroutemonitor.h
Normal file
53
client/platforms/linux/daemon/linuxroutemonitor.h
Normal 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
|
228
client/platforms/linux/daemon/pidtracker.cpp
Normal file
228
client/platforms/linux/daemon/pidtracker.cpp
Normal 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;
|
||||
}
|
72
client/platforms/linux/daemon/pidtracker.h
Normal file
72
client/platforms/linux/daemon/pidtracker.h
Normal 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
|
369
client/platforms/linux/daemon/wireguardutilslinux.cpp
Normal file
369
client/platforms/linux/daemon/wireguardutilslinux.cpp
Normal 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();
|
||||
}
|
55
client/platforms/linux/daemon/wireguardutilslinux.h
Normal file
55
client/platforms/linux/daemon/wireguardutilslinux.h
Normal 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
|
47
client/platforms/linux/interfaceconfig.h
Normal file
47
client/platforms/linux/interfaceconfig.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/* 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 INTERFACECONFIG_H
|
||||
#define INTERFACECONFIG_H
|
||||
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "ipaddress.h"
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
class InterfaceConfig {
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
InterfaceConfig() {}
|
||||
|
||||
enum HopType { SingleHop, MultiHopEntry, MultiHopExit };
|
||||
Q_ENUM(HopType)
|
||||
|
||||
HopType m_hopType;
|
||||
QString m_privateKey;
|
||||
QString m_deviceIpv4Address;
|
||||
QString m_deviceIpv6Address;
|
||||
QString m_serverIpv4Gateway;
|
||||
QString m_serverIpv6Gateway;
|
||||
QString m_serverPublicKey;
|
||||
QString m_serverIpv4AddrIn;
|
||||
QString m_serverIpv6AddrIn;
|
||||
QString m_dnsServer;
|
||||
int m_serverPort = 0;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||
QString m_installationId;
|
||||
#endif
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
|
||||
};
|
||||
|
||||
#endif // INTERFACECONFIG_H
|
139
client/platforms/linux/linuxdependencies.cpp
Normal file
139
client/platforms/linux/linuxdependencies.cpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
/* 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 "linuxdependencies.h"
|
||||
|
||||
#include <mntent.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QDBusInterface>
|
||||
#include <QCoreApplication>
|
||||
#include <QProcess>
|
||||
|
||||
//#include "dbusclient.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger("LinuxDependencies");
|
||||
|
||||
void showAlert(const QString& message) {
|
||||
logger.debug() << "Show alert:" << message;
|
||||
|
||||
QMessageBox alert;
|
||||
alert.setText(message);
|
||||
alert.exec();
|
||||
}
|
||||
|
||||
bool checkDaemonVersion() {
|
||||
logger.debug() << "Check Daemon Version";
|
||||
|
||||
bool completed = false;
|
||||
bool value = false;
|
||||
|
||||
while (!completed) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
bool LinuxDependencies::checkDependencies() {
|
||||
char* path = getenv("PATH");
|
||||
if (!path) {
|
||||
showAlert("No PATH env found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkDaemonVersion()) {
|
||||
showAlert("mozillavpn linuxdaemon needs to be updated or restarted.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
QString LinuxDependencies::findCgroupPath(const QString& type) {
|
||||
struct mntent entry;
|
||||
char buf[PATH_MAX];
|
||||
|
||||
FILE* fp = fopen("/etc/mtab", "r");
|
||||
if (fp == NULL) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
while (getmntent_r(fp, &entry, buf, sizeof(buf)) != NULL) {
|
||||
if (strcmp(entry.mnt_type, "cgroup") != 0) {
|
||||
continue;
|
||||
}
|
||||
if (hasmntopt(&entry, type.toLocal8Bit().constData()) != NULL) {
|
||||
fclose(fp);
|
||||
return QString(entry.mnt_dir);
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
// static
|
||||
QString LinuxDependencies::findCgroup2Path() {
|
||||
struct mntent entry;
|
||||
char buf[PATH_MAX];
|
||||
|
||||
FILE* fp = fopen("/etc/mtab", "r");
|
||||
if (fp == NULL) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
while (getmntent_r(fp, &entry, buf, sizeof(buf)) != NULL) {
|
||||
if (strcmp(entry.mnt_type, "cgroup2") != 0) {
|
||||
continue;
|
||||
}
|
||||
return QString(entry.mnt_dir);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
// static
|
||||
QString LinuxDependencies::gnomeShellVersion() {
|
||||
QDBusInterface iface("org.gnome.Shell", "/org/gnome/Shell",
|
||||
"org.gnome.Shell");
|
||||
if (!iface.isValid()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QVariant shellVersion = iface.property("ShellVersion");
|
||||
if (!shellVersion.isValid()) {
|
||||
return QString();
|
||||
}
|
||||
return shellVersion.toString();
|
||||
}
|
||||
|
||||
// static
|
||||
QString LinuxDependencies::kdeFrameworkVersion() {
|
||||
QProcess proc;
|
||||
proc.start("kf5-config", QStringList{"--version"}, QIODeviceBase::ReadOnly);
|
||||
if (!proc.waitForFinished()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QByteArray result = proc.readAllStandardOutput();
|
||||
for (const QByteArray& line : result.split('\n')) {
|
||||
if (line.startsWith("KDE Frameworks: ")) {
|
||||
return QString::fromUtf8(line.last(line.size() - 16));
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
25
client/platforms/linux/linuxdependencies.h
Normal file
25
client/platforms/linux/linuxdependencies.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/* 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 LINUXDEPENDENCIES_H
|
||||
#define LINUXDEPENDENCIES_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class LinuxDependencies final {
|
||||
public:
|
||||
static bool checkDependencies();
|
||||
static QString findCgroupPath(const QString& type);
|
||||
static QString findCgroup2Path();
|
||||
static QString gnomeShellVersion();
|
||||
static QString kdeFrameworkVersion();
|
||||
|
||||
private:
|
||||
LinuxDependencies() = default;
|
||||
~LinuxDependencies() = default;
|
||||
|
||||
Q_DISABLE_COPY(LinuxDependencies)
|
||||
};
|
||||
|
||||
#endif // LINUXDEPENDENCIES_H
|
57
client/platforms/linux/linuxnetworkwatcher.cpp
Normal file
57
client/platforms/linux/linuxnetworkwatcher.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/* 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 "linuxnetworkwatcher.h"
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "linuxnetworkwatcherworker.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("LinuxNetworkWatcher");
|
||||
}
|
||||
|
||||
LinuxNetworkWatcher::LinuxNetworkWatcher(QObject* parent)
|
||||
: NetworkWatcherImpl(parent) {
|
||||
MZ_COUNT_CTOR(LinuxNetworkWatcher);
|
||||
|
||||
m_thread.start();
|
||||
}
|
||||
|
||||
LinuxNetworkWatcher::~LinuxNetworkWatcher() {
|
||||
MZ_COUNT_DTOR(LinuxNetworkWatcher);
|
||||
|
||||
delete m_worker;
|
||||
|
||||
m_thread.quit();
|
||||
m_thread.wait();
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcher::initialize() {
|
||||
logger.debug() << "initialize";
|
||||
|
||||
m_worker = new LinuxNetworkWatcherWorker(&m_thread);
|
||||
|
||||
connect(this, &LinuxNetworkWatcher::checkDevicesInThread, m_worker,
|
||||
&LinuxNetworkWatcherWorker::checkDevices);
|
||||
|
||||
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
||||
&LinuxNetworkWatcher::unsecuredNetwork);
|
||||
|
||||
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
||||
// This is not strictly needed, but it's better for user experience because
|
||||
// it makes the UI faster to appear, plus it gives a bit of delay between the
|
||||
// UI to appear and the first notification.
|
||||
QTimer::singleShot(2000, this, [this]() {
|
||||
QMetaObject::invokeMethod(m_worker, "initialize", Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcher::start() {
|
||||
logger.debug() << "actived";
|
||||
NetworkWatcherImpl::start();
|
||||
emit checkDevicesInThread();
|
||||
}
|
38
client/platforms/linux/linuxnetworkwatcher.h
Normal file
38
client/platforms/linux/linuxnetworkwatcher.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/* 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 LINUXNETWORKWATCHER_H
|
||||
#define LINUXNETWORKWATCHER_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include "networkwatcherimpl.h"
|
||||
|
||||
class LinuxNetworkWatcherWorker;
|
||||
|
||||
class LinuxNetworkWatcher final : public NetworkWatcherImpl {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LinuxNetworkWatcher(QObject* parent);
|
||||
~LinuxNetworkWatcher();
|
||||
|
||||
void initialize() override;
|
||||
|
||||
void start() override;
|
||||
|
||||
NetworkWatcherImpl::TransportType getTransportType() {
|
||||
// TODO: Find out how to do that on linux generally. (VPN-2382)
|
||||
return NetworkWatcherImpl::TransportType_Unknown;
|
||||
};
|
||||
|
||||
signals:
|
||||
void checkDevicesInThread();
|
||||
|
||||
private:
|
||||
LinuxNetworkWatcherWorker* m_worker = nullptr;
|
||||
QThread m_thread;
|
||||
};
|
||||
|
||||
#endif // LINUXNETWORKWATCHER_H
|
177
client/platforms/linux/linuxnetworkwatcherworker.cpp
Normal file
177
client/platforms/linux/linuxnetworkwatcherworker.cpp
Normal file
|
@ -0,0 +1,177 @@
|
|||
/* 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 "linuxnetworkwatcherworker.h"
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
// https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceType
|
||||
#ifndef NM_DEVICE_TYPE_WIFI
|
||||
# define NM_DEVICE_TYPE_WIFI 2
|
||||
#endif
|
||||
|
||||
// https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NM80211ApFlags
|
||||
// Wifi network has no security
|
||||
#ifndef NM_802_11_AP_SEC_NONE
|
||||
# define NM_802_11_AP_SEC_NONE 0x00000000
|
||||
#endif
|
||||
|
||||
// Wifi network has WEP (40 bits)
|
||||
#ifndef NM_802_11_AP_SEC_PAIR_WEP40
|
||||
# define NM_802_11_AP_SEC_PAIR_WEP40 0x00000001
|
||||
#endif
|
||||
|
||||
// Wifi network has WEP (104 bits)
|
||||
#ifndef NM_802_11_AP_SEC_PAIR_WEP104
|
||||
# define NM_802_11_AP_SEC_PAIR_WEP104 0x00000002
|
||||
#endif
|
||||
|
||||
#define NM_802_11_AP_SEC_WEAK_CRYPTO \
|
||||
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
|
||||
|
||||
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
|
||||
|
||||
namespace {
|
||||
Logger logger("LinuxNetworkWatcherWorker");
|
||||
}
|
||||
|
||||
static inline bool checkUnsecureFlags(int rsnFlags, int wpaFlags) {
|
||||
// If neither WPA nor WPA2/RSN are supported, then the network is unencrypted
|
||||
if (rsnFlags == NM_802_11_AP_SEC_NONE && wpaFlags == NM_802_11_AP_SEC_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Consider the user of weak cryptography to be unsecure
|
||||
if ((rsnFlags & NM_802_11_AP_SEC_WEAK_CRYPTO) ||
|
||||
(wpaFlags & NM_802_11_AP_SEC_WEAK_CRYPTO)) {
|
||||
return false;
|
||||
}
|
||||
// Otherwise, the network is secured with reasonable cryptography
|
||||
return true;
|
||||
}
|
||||
|
||||
LinuxNetworkWatcherWorker::LinuxNetworkWatcherWorker(QThread* thread) {
|
||||
MZ_COUNT_CTOR(LinuxNetworkWatcherWorker);
|
||||
moveToThread(thread);
|
||||
}
|
||||
|
||||
LinuxNetworkWatcherWorker::~LinuxNetworkWatcherWorker() {
|
||||
MZ_COUNT_DTOR(LinuxNetworkWatcherWorker);
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcherWorker::initialize() {
|
||||
logger.debug() << "initialize";
|
||||
|
||||
logger.debug()
|
||||
<< "Retrieving the list of wifi network devices from NetworkManager";
|
||||
|
||||
// To know the NeworkManager DBus methods and properties, read the official
|
||||
// documentation:
|
||||
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
|
||||
|
||||
QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager",
|
||||
DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
|
||||
if (!nm.isValid()) {
|
||||
logger.error()
|
||||
<< "Failed to connect to the network manager via system dbus";
|
||||
return;
|
||||
}
|
||||
|
||||
QDBusMessage msg = nm.call("GetDevices");
|
||||
QDBusArgument arg = msg.arguments().at(0).value<QDBusArgument>();
|
||||
if (arg.currentType() != QDBusArgument::ArrayType) {
|
||||
logger.error() << "Expected an array of devices";
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QDBusObjectPath> paths = qdbus_cast<QList<QDBusObjectPath> >(arg);
|
||||
for (const QDBusObjectPath& path : paths) {
|
||||
QString devicePath = path.path();
|
||||
QDBusInterface device(DBUS_NETWORKMANAGER, devicePath,
|
||||
"org.freedesktop.NetworkManager.Device",
|
||||
QDBusConnection::systemBus());
|
||||
if (device.property("DeviceType").toInt() != NM_DEVICE_TYPE_WIFI) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug() << "Found a wifi device:" << devicePath;
|
||||
m_devicePaths.append(devicePath);
|
||||
|
||||
// Here we monitor the changes.
|
||||
QDBusConnection::systemBus().connect(
|
||||
DBUS_NETWORKMANAGER, devicePath, "org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged", this,
|
||||
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
|
||||
}
|
||||
|
||||
if (m_devicePaths.isEmpty()) {
|
||||
logger.warning() << "No wifi devices found";
|
||||
return;
|
||||
}
|
||||
|
||||
// We could be already be activated.
|
||||
checkDevices();
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcherWorker::propertyChanged(QString interface,
|
||||
QVariantMap properties,
|
||||
QStringList list) {
|
||||
Q_UNUSED(list);
|
||||
|
||||
logger.debug() << "Properties changed for interface" << interface;
|
||||
|
||||
if (!properties.contains("ActiveAccessPoint")) {
|
||||
logger.debug() << "Access point did not changed. Ignoring the changes";
|
||||
return;
|
||||
}
|
||||
|
||||
checkDevices();
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcherWorker::checkDevices() {
|
||||
logger.debug() << "Checking devices";
|
||||
|
||||
for (const QString& devicePath : m_devicePaths) {
|
||||
QDBusInterface wifiDevice(DBUS_NETWORKMANAGER, devicePath,
|
||||
"org.freedesktop.NetworkManager.Device.Wireless",
|
||||
QDBusConnection::systemBus());
|
||||
|
||||
// Check the access point path
|
||||
QString accessPointPath = wifiDevice.property("ActiveAccessPoint")
|
||||
.value<QDBusObjectPath>()
|
||||
.path();
|
||||
if (accessPointPath.isEmpty()) {
|
||||
logger.warning() << "No access point found";
|
||||
continue;
|
||||
}
|
||||
|
||||
QDBusInterface ap(DBUS_NETWORKMANAGER, accessPointPath,
|
||||
"org.freedesktop.NetworkManager.AccessPoint",
|
||||
QDBusConnection::systemBus());
|
||||
|
||||
QVariant rsnFlags = ap.property("RsnFlags");
|
||||
QVariant wpaFlags = ap.property("WpaFlags");
|
||||
if (!rsnFlags.isValid() || !wpaFlags.isValid()) {
|
||||
// We are probably not connected.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!checkUnsecureFlags(rsnFlags.toInt(), wpaFlags.toInt())) {
|
||||
QString ssid = ap.property("Ssid").toString();
|
||||
QString bssid = ap.property("HwAddress").toString();
|
||||
|
||||
// We have found 1 unsecured network. We don't need to check other wifi
|
||||
// network devices.
|
||||
logger.warning() << "Unsecured AP detected!"
|
||||
<< "rsnFlags:" << rsnFlags.toInt()
|
||||
<< "wpaFlags:" << wpaFlags.toInt()
|
||||
<< "ssid:" << logger.sensitive(ssid);
|
||||
emit unsecuredNetwork(ssid, bssid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
41
client/platforms/linux/linuxnetworkwatcherworker.h
Normal file
41
client/platforms/linux/linuxnetworkwatcherworker.h
Normal 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 LINUXNETWORKWATCHERWORKER_H
|
||||
#define LINUXNETWORKWATCHERWORKER_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
class QThread;
|
||||
|
||||
class LinuxNetworkWatcherWorker final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(LinuxNetworkWatcherWorker)
|
||||
|
||||
public:
|
||||
explicit LinuxNetworkWatcherWorker(QThread* thread);
|
||||
~LinuxNetworkWatcherWorker();
|
||||
|
||||
void checkDevices();
|
||||
|
||||
signals:
|
||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||
|
||||
public slots:
|
||||
void initialize();
|
||||
|
||||
private slots:
|
||||
void propertyChanged(QString interface, QVariantMap properties,
|
||||
QStringList list);
|
||||
|
||||
private:
|
||||
// We collect the list of DBus wifi network device paths during the
|
||||
// initialization. When a property of them changes, we check if the access
|
||||
// point is active and unsecure.
|
||||
QStringList m_devicePaths;
|
||||
};
|
||||
|
||||
#endif // LINUXNETWORKWATCHERWORKER_H
|
|
@ -16,9 +16,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject*
|
|||
writeWireguardConfiguration(configuration);
|
||||
|
||||
// MZ
|
||||
#if defined(MZ_LINUX)
|
||||
//m_impl.reset(new LinuxController());
|
||||
#elif defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
m_impl.reset(new LocalSocketController());
|
||||
connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) {
|
||||
emit connectionStateChanged(VpnProtocol::Connected);
|
||||
|
@ -38,7 +36,7 @@ WireguardProtocol::~WireguardProtocol()
|
|||
|
||||
void WireguardProtocol::stop()
|
||||
{
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
stopMzImpl();
|
||||
return;
|
||||
#endif
|
||||
|
@ -98,7 +96,7 @@ void WireguardProtocol::stop()
|
|||
setConnectionState(VpnProtocol::Disconnected);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
ErrorCode WireguardProtocol::startMzImpl()
|
||||
{
|
||||
|
||||
|
@ -126,7 +124,7 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura
|
|||
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
|
||||
m_configFile.close();
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#if 0
|
||||
if (IpcClient::Interface()) {
|
||||
QRemoteObjectPendingReply<bool> result = IpcClient::Interface()->copyWireguardConfig(m_configFile.fileName());
|
||||
if (result.returnValue()) {
|
||||
|
@ -171,7 +169,7 @@ ErrorCode WireguardProtocol::start()
|
|||
return lastError();
|
||||
}
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
return startMzImpl();
|
||||
#endif
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
ErrorCode startMzImpl();
|
||||
ErrorCode stopMzImpl();
|
||||
#endif
|
||||
|
@ -47,7 +47,7 @@ private:
|
|||
|
||||
bool m_isConfigLoaded = false;
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
QScopedPointer<ControllerImpl> m_impl;
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -6,9 +6,10 @@ project(${PROJECT})
|
|||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Network Widgets RemoteObjects Core5Compat)
|
||||
find_package(Qt6 REQUIRED COMPONENTS DBus Core Network Widgets RemoteObjects Core5Compat)
|
||||
qt_standard_project_setup()
|
||||
|
||||
|
||||
configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
|
||||
set(HEADERS
|
||||
|
@ -91,7 +92,7 @@ if(UNIX)
|
|||
)
|
||||
endif()
|
||||
|
||||
if (WIN32 OR APPLE)
|
||||
if (WIN32 OR APPLE OR LINUX)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemon.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonlocalserver.h
|
||||
|
@ -198,12 +199,32 @@ if(APPLE)
|
|||
endif()
|
||||
|
||||
if(LINUX)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/router_linux.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcher.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcherworker.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxdependencies.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/iputilslinux.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dbustypeslinux.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/pidtracker.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/router_linux.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcher.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcherworker.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxdependencies.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/pidtracker.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/iputilslinux.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
|
@ -217,7 +238,7 @@ include_directories(
|
|||
)
|
||||
|
||||
add_executable(${PROJECT} ${SOURCES} ${HEADERS})
|
||||
target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat ${LIBS})
|
||||
target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus ${LIBS})
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
|
|
|
@ -40,12 +40,16 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
|||
}
|
||||
});
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
// Init Mozilla Wireguard Daemon
|
||||
if (!server.initialize()) {
|
||||
logger.error() << "Failed to initialize the server";
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// Signal handling for a proper shutdown.
|
||||
QObject::connect(qApp, &QCoreApplication::aboutToQuit,
|
||||
[]() { LinuxDaemon::instance()->deactivate(); });
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
|
|
|
@ -10,13 +10,18 @@
|
|||
|
||||
#include "ipcserver.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "../../client/daemon/daemonlocalserver.h"
|
||||
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "windows/daemon/windowsdaemon.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include "linux/daemon/linuxdaemon.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include "../../client/daemon/daemonlocalserver.h"
|
||||
#include "macos/daemon/macosdaemon.h"
|
||||
#endif
|
||||
|
||||
|
@ -31,13 +36,14 @@ class LocalServer : public QObject
|
|||
public:
|
||||
explicit LocalServer(QObject* parent = nullptr);
|
||||
~LocalServer();
|
||||
|
||||
QSharedPointer<QLocalServer> m_server;
|
||||
|
||||
IpcServer m_ipcServer;
|
||||
QRemoteObjectHost m_serverNode;
|
||||
bool m_isRemotingEnabled = false;
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
DaemonLocalServer server{qApp};
|
||||
LinuxDaemon daemon;
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
DaemonLocalServer server{qApp};
|
||||
WindowsDaemon daemon;
|
||||
|
|
|
@ -14,6 +14,17 @@
|
|||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <net/if.h>
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
RouterLinux &RouterLinux::Instance()
|
||||
|
@ -22,6 +33,123 @@ RouterLinux &RouterLinux::Instance()
|
|||
return s;
|
||||
}
|
||||
|
||||
#define BUFFER_SIZE 4096
|
||||
|
||||
QString RouterLinux::getgatewayandiface()
|
||||
{
|
||||
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||
int sock = -1, msgseq = 0;
|
||||
struct nlmsghdr *nlh, *nlmsg;
|
||||
struct rtmsg *route_entry;
|
||||
// This struct contain route attributes (route type)
|
||||
struct rtattr *route_attribute;
|
||||
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||
char *ptr = buffer;
|
||||
struct timeval tv;
|
||||
|
||||
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||
perror("socket failed");
|
||||
return "";
|
||||
}
|
||||
|
||||
memset(msgbuf, 0, sizeof(msgbuf));
|
||||
memset(gateway_address, 0, sizeof(gateway_address));
|
||||
memset(interface, 0, sizeof(interface));
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
/* point the header and the msg structure pointers into the buffer */
|
||||
nlmsg = (struct nlmsghdr *)msgbuf;
|
||||
|
||||
/* Fill in the nlmsg header*/
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
|
||||
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
|
||||
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
|
||||
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
|
||||
|
||||
/* 1 Sec Timeout to avoid stall */
|
||||
tv.tv_sec = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
|
||||
/* send msg */
|
||||
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||
perror("send failed");
|
||||
return "";
|
||||
}
|
||||
|
||||
/* receive response */
|
||||
do
|
||||
{
|
||||
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||
if (received_bytes < 0) {
|
||||
perror("Error in recv");
|
||||
return "";
|
||||
}
|
||||
|
||||
nlh = (struct nlmsghdr *) ptr;
|
||||
|
||||
/* Check if the header is valid */
|
||||
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return "";
|
||||
}
|
||||
|
||||
/* If we received all data break */
|
||||
if (nlh->nlmsg_type == NLMSG_DONE)
|
||||
break;
|
||||
else {
|
||||
ptr += received_bytes;
|
||||
msg_len += received_bytes;
|
||||
}
|
||||
|
||||
/* Break if its not a multi part message */
|
||||
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
break;
|
||||
}
|
||||
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||
|
||||
/* parse response */
|
||||
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||
{
|
||||
/* Get the route data */
|
||||
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||
|
||||
/* We are just interested in main routing table */
|
||||
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||
continue;
|
||||
|
||||
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||
|
||||
/* Loop through all attributes */
|
||||
for ( ; RTA_OK(route_attribute, route_attribute_len);
|
||||
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
|
||||
{
|
||||
switch(route_attribute->rta_type) {
|
||||
case RTA_OIF:
|
||||
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
|
||||
break;
|
||||
case RTA_GATEWAY:
|
||||
inet_ntop(AF_INET, RTA_DATA(route_attribute),
|
||||
gateway_address, sizeof(gateway_address));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((*gateway_address) && (*interface)) {
|
||||
qDebug().noquote() << "Gateway " << gateway_address << " for interface " << interface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
close(sock);
|
||||
return gateway_address;
|
||||
}
|
||||
|
||||
|
||||
bool RouterLinux::routeAdd(const QString &ipWithSubnet, const QString &gw, const int &sock)
|
||||
{
|
||||
QString ip = Utils::ipAddressFromIpWithSubnet(ipWithSubnet);
|
||||
|
@ -29,7 +157,7 @@ bool RouterLinux::routeAdd(const QString &ipWithSubnet, const QString &gw, const
|
|||
|
||||
if (!Utils::checkIPv4Format(ip) || !Utils::checkIPv4Format(gw)) {
|
||||
qCritical().noquote() << "Critical, trying to add invalid route: " << ip << gw;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct rtentry route;
|
||||
|
@ -53,11 +181,11 @@ bool RouterLinux::routeAdd(const QString &ipWithSubnet, const QString &gw, const
|
|||
|
||||
if (int err = ioctl(sock, SIOCADDRT, &route) < 0)
|
||||
{
|
||||
qDebug().noquote() << "route add error: gw "
|
||||
<< ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr
|
||||
<< " ip " << ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr
|
||||
<< " mask " << ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr << " " << err;
|
||||
return false;
|
||||
// qDebug().noquote() << "route add error: gw "
|
||||
// << ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr
|
||||
// << " ip " << ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr
|
||||
// << " mask " << ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr << " " << err;
|
||||
// return false;
|
||||
}
|
||||
|
||||
m_addedRoutes.append({ipWithSubnet, gw});
|
||||
|
@ -99,7 +227,7 @@ bool RouterLinux::routeDelete(const QString &ipWithSubnet, const QString &gw, co
|
|||
|
||||
if (!Utils::checkIPv4Format(ip) || !Utils::checkIPv4Format(gw)) {
|
||||
qCritical().noquote() << "Critical, trying to remove invalid route: " << ip << gw;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ip == "0.0.0.0") {
|
||||
|
@ -129,8 +257,8 @@ bool RouterLinux::routeDelete(const QString &ipWithSubnet, const QString &gw, co
|
|||
|
||||
if (ioctl(sock, SIOCDELRT, &route) < 0)
|
||||
{
|
||||
qDebug().noquote() << "route delete error: gw " << gw << " ip " << ip << " mask " << mask;
|
||||
return false;
|
||||
// qDebug().noquote() << "route delete error: gw " << gw << " ip " << ip << " mask " << mask;
|
||||
// return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ public:
|
|||
bool clearSavedRoutes();
|
||||
bool routeDelete(const QString &ip, const QString &gw, const int &sock);
|
||||
bool routeDeleteList(const QString &gw, const QStringList &ips);
|
||||
QString getgatewayandiface();
|
||||
void flushDns();
|
||||
|
||||
public slots:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue