cleaned up
This commit is contained in:
parent
d61bfaa993
commit
a59077b94f
20 changed files with 0 additions and 2313 deletions
|
@ -1,109 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "apptracker.h"
|
||||
#include "dbustypeslinux.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusInterface>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
constexpr const char* GTK_DESKTOP_APP_SERVICE = "org.gtk.gio.DesktopAppInfo";
|
||||
constexpr const char* GTK_DESKTOP_APP_PATH = "/org/gtk/gio/DesktopAppInfo";
|
||||
|
||||
constexpr const char* DBUS_LOGIN_SERVICE = "org.freedesktop.login1";
|
||||
constexpr const char* DBUS_LOGIN_PATH = "/org/freedesktop/login1";
|
||||
constexpr const char* DBUS_LOGIN_MANAGER = "org.freedesktop.login1.Manager";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "AppTracker");
|
||||
}
|
||||
|
||||
AppTracker::AppTracker(QObject* parent) : QObject(parent) {
|
||||
MVPN_COUNT_CTOR(AppTracker);
|
||||
logger.debug() << "AppTracker created.";
|
||||
|
||||
QDBusConnection m_conn = QDBusConnection::systemBus();
|
||||
m_conn.connect("", DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER, "UserNew", this,
|
||||
SLOT(userCreated(uint, const QDBusObjectPath&)));
|
||||
m_conn.connect("", DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER, "UserRemoved", this,
|
||||
SLOT(userRemoved(uint, const QDBusObjectPath&)));
|
||||
|
||||
QDBusInterface n(DBUS_LOGIN_SERVICE, DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER,
|
||||
m_conn);
|
||||
QDBusPendingReply<UserDataList> reply = n.asyncCall("ListUsers");
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||
SLOT(userListCompleted(QDBusPendingCallWatcher*)));
|
||||
}
|
||||
|
||||
AppTracker::~AppTracker() {
|
||||
MVPN_COUNT_DTOR(AppTracker);
|
||||
logger.debug() << "AppTracker destroyed.";
|
||||
}
|
||||
|
||||
void AppTracker::userListCompleted(QDBusPendingCallWatcher* watcher) {
|
||||
QDBusPendingReply<UserDataList> reply = *watcher;
|
||||
if (reply.isValid()) {
|
||||
UserDataList list = reply.value();
|
||||
for (auto user : list) {
|
||||
userCreated(user.userid, user.path);
|
||||
}
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
void AppTracker::userCreated(uint userid, const QDBusObjectPath& path) {
|
||||
logger.debug() << "User created uid:" << userid << "at:" << path.path();
|
||||
|
||||
/* Acquire the effective UID of the user to connect to their session bus. */
|
||||
uid_t realuid = getuid();
|
||||
if (seteuid(userid) < 0) {
|
||||
logger.warning() << "Failed to set effective UID";
|
||||
}
|
||||
auto guard = qScopeGuard([&] {
|
||||
if (seteuid(realuid) < 0) {
|
||||
logger.warning() << "Failed to restore effective UID";
|
||||
}
|
||||
});
|
||||
|
||||
/* For correctness we should ask systemd for the user's runtime directory. */
|
||||
QString busPath = "unix:path=/run/user/" + QString::number(userid) + "/bus";
|
||||
logger.debug() << "Connection to" << busPath;
|
||||
QDBusConnection connection =
|
||||
QDBusConnection::connectToBus(busPath, "user-" + QString::number(userid));
|
||||
|
||||
/* Connect to the user's GTK launch event. */
|
||||
bool isConnected = connection.connect(
|
||||
"", GTK_DESKTOP_APP_PATH, GTK_DESKTOP_APP_SERVICE, "Launched", this,
|
||||
SLOT(gtkLaunchEvent(const QByteArray&, const QString&, qlonglong,
|
||||
const QStringList&, const QVariantMap&)));
|
||||
if (!isConnected) {
|
||||
logger.warning() << "Failed to connect to GTK Launched signal";
|
||||
}
|
||||
}
|
||||
|
||||
void AppTracker::userRemoved(uint uid, const QDBusObjectPath& path) {
|
||||
logger.debug() << "User removed uid:" << uid << "at:" << path.path();
|
||||
QDBusConnection::disconnectFromBus("user-" + QString::number(uid));
|
||||
}
|
||||
|
||||
void AppTracker::gtkLaunchEvent(const QByteArray& appid, const QString& display,
|
||||
qlonglong pid, const QStringList& uris,
|
||||
const QVariantMap& extra) {
|
||||
Q_UNUSED(display);
|
||||
Q_UNUSED(uris);
|
||||
Q_UNUSED(extra);
|
||||
|
||||
QString appIdName(appid);
|
||||
if (!appIdName.isEmpty()) {
|
||||
emit appLaunched(appIdName, pid);
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef APPTRACKER_H
|
||||
#define APPTRACKER_H
|
||||
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusObjectPath>
|
||||
|
||||
class AppTracker final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(AppTracker)
|
||||
|
||||
public:
|
||||
explicit AppTracker(QObject* parent);
|
||||
~AppTracker();
|
||||
|
||||
signals:
|
||||
void appLaunched(const QString& name, int rootpid);
|
||||
|
||||
private slots:
|
||||
void userListCompleted(QDBusPendingCallWatcher* call);
|
||||
void userCreated(uint uid, const QDBusObjectPath& path);
|
||||
void userRemoved(uint uid, const QDBusObjectPath& path);
|
||||
void gtkLaunchEvent(const QByteArray& appid, const QString& display,
|
||||
qlonglong pid, const QStringList& uris,
|
||||
const QVariantMap& extra);
|
||||
};
|
||||
|
||||
#endif // APPTRACKER_H
|
|
@ -1,249 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "dbusservice.h"
|
||||
#include "dbus_adaptor.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "loghandler.h"
|
||||
#include "polkithelper.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "DBusService");
|
||||
}
|
||||
|
||||
constexpr const char* APP_STATE_ACTIVE = "active";
|
||||
constexpr const char* APP_STATE_EXCLUDED = "excluded";
|
||||
constexpr const char* APP_STATE_BLOCKED = "blocked";
|
||||
|
||||
DBusService::DBusService(QObject* parent) : Daemon(parent) {
|
||||
MVPN_COUNT_CTOR(DBusService);
|
||||
|
||||
m_wgutils = new WireguardUtilsLinux(this);
|
||||
m_apptracker = new AppTracker(this);
|
||||
m_pidtracker = new PidTracker(this);
|
||||
|
||||
connect(m_apptracker, SIGNAL(appLaunched(const QString&, int)), this,
|
||||
SLOT(appLaunched(const QString&, int)));
|
||||
connect(m_pidtracker, SIGNAL(terminated(const QString&, int)), this,
|
||||
SLOT(appTerminated(const QString&, int)));
|
||||
|
||||
if (!removeInterfaceIfExists()) {
|
||||
qFatal("Interface `%s` exists and cannot be removed. Cannot proceed!",
|
||||
WG_INTERFACE);
|
||||
}
|
||||
}
|
||||
|
||||
DBusService::~DBusService() { MVPN_COUNT_DTOR(DBusService); }
|
||||
|
||||
IPUtils* DBusService::iputils() {
|
||||
if (!m_iputils) {
|
||||
m_iputils = new IPUtilsLinux(this);
|
||||
}
|
||||
return m_iputils;
|
||||
}
|
||||
|
||||
DnsUtils* DBusService::dnsutils() {
|
||||
if (!m_dnsutils) {
|
||||
m_dnsutils = new DnsUtilsLinux(this);
|
||||
}
|
||||
return m_dnsutils;
|
||||
}
|
||||
|
||||
void DBusService::setAdaptor(DbusAdaptor* adaptor) {
|
||||
Q_ASSERT(!m_adaptor);
|
||||
m_adaptor = adaptor;
|
||||
}
|
||||
|
||||
bool DBusService::removeInterfaceIfExists() {
|
||||
if (m_wgutils->interfaceExists()) {
|
||||
logger.warning() << "Device already exists. Let's remove it.";
|
||||
if (!m_wgutils->deleteInterface()) {
|
||||
logger.error() << "Failed to remove the device.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DBusService::version() {
|
||||
logger.debug() << "Version request";
|
||||
return PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
bool DBusService::activate(const QString& jsonConfig) {
|
||||
logger.debug() << "Activate";
|
||||
|
||||
if (!PolkitHelper::instance()->checkAuthorization(
|
||||
"org.mozilla.vpn.activate")) {
|
||||
logger.error() << "Polkit rejected";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(jsonConfig.toLocal8Bit());
|
||||
if (!json.isObject()) {
|
||||
logger.error() << "Invalid input";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject obj = json.object();
|
||||
|
||||
InterfaceConfig config;
|
||||
if (!parseConfig(obj, config)) {
|
||||
logger.error() << "Invalid configuration";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj.contains("vpnDisabledApps")) {
|
||||
QJsonArray disabledApps = obj["vpnDisabledApps"].toArray();
|
||||
for (const QJsonValue& app : disabledApps) {
|
||||
firewallApp(app.toString(), APP_STATE_EXCLUDED);
|
||||
}
|
||||
}
|
||||
|
||||
return Daemon::activate(config);
|
||||
}
|
||||
|
||||
bool DBusService::deactivate(bool emitSignals) {
|
||||
logger.debug() << "Deactivate";
|
||||
firewallClear();
|
||||
return Daemon::deactivate(emitSignals);
|
||||
}
|
||||
|
||||
QString DBusService::status() { return QString(getStatus()); }
|
||||
|
||||
QByteArray DBusService::getStatus() {
|
||||
logger.debug() << "Status request";
|
||||
QJsonObject json;
|
||||
|
||||
if (!m_connections.contains(0)) {
|
||||
json.insert("status", QJsonValue(false));
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
const InterfaceConfig& config = m_connections.value(0).m_config;
|
||||
if (!m_wgutils->interfaceExists()) {
|
||||
logger.error() << "Unable to get device";
|
||||
json.insert("status", QJsonValue(false));
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
json.insert("status", QJsonValue(true));
|
||||
json.insert("serverIpv4Gateway", QJsonValue(config.m_serverIpv4Gateway));
|
||||
json.insert("deviceIpv4Address", QJsonValue(config.m_deviceIpv4Address));
|
||||
WireguardUtilsLinux::peerStatus status =
|
||||
m_wgutils->getPeerStatus(config.m_serverPublicKey);
|
||||
json.insert("txBytes", QJsonValue(status.txBytes));
|
||||
json.insert("rxBytes", QJsonValue(status.rxBytes));
|
||||
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
QString DBusService::getLogs() {
|
||||
logger.debug() << "Log request";
|
||||
return Daemon::logs();
|
||||
}
|
||||
|
||||
void DBusService::appLaunched(const QString& name, int rootpid) {
|
||||
logger.debug() << "tracking:" << name << "PID:" << rootpid;
|
||||
ProcessGroup* group = m_pidtracker->track(name, rootpid);
|
||||
if (m_firewallApps.contains(name)) {
|
||||
group->state = m_firewallApps[name];
|
||||
group->moveToCgroup(getAppStateCgroup(group->state));
|
||||
}
|
||||
}
|
||||
|
||||
void DBusService::appTerminated(const QString& name, int rootpid) {
|
||||
logger.debug() << "terminate:" << name << "PID:" << rootpid;
|
||||
}
|
||||
|
||||
/* Get the list of running applications that the firewall knows about. */
|
||||
QString DBusService::runningApps() {
|
||||
QJsonArray result;
|
||||
for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) {
|
||||
const ProcessGroup* group = *i;
|
||||
QJsonObject appObject;
|
||||
QJsonArray pidList;
|
||||
appObject.insert("name", QJsonValue(group->name));
|
||||
appObject.insert("rootpid", QJsonValue(group->rootpid));
|
||||
appObject.insert("state", QJsonValue(group->state));
|
||||
|
||||
for (auto pid : group->kthreads.keys()) {
|
||||
pidList.append(QJsonValue(pid));
|
||||
}
|
||||
|
||||
appObject.insert("pids", pidList);
|
||||
result.append(appObject);
|
||||
}
|
||||
|
||||
return QJsonDocument(result).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
/* Update the firewall for running applications matching the application ID. */
|
||||
bool DBusService::firewallApp(const QString& appName, const QString& state) {
|
||||
logger.debug() << "Setting" << appName << "to firewall state" << state;
|
||||
m_firewallApps[appName] = state;
|
||||
QString cgroup = getAppStateCgroup(state);
|
||||
|
||||
/* Change matching applications' state to excluded */
|
||||
for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) {
|
||||
ProcessGroup* group = *i;
|
||||
if (group->name != appName) {
|
||||
continue;
|
||||
}
|
||||
group->state = state;
|
||||
group->moveToCgroup(cgroup);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Update the firewall for the application matching the desired PID. */
|
||||
bool DBusService::firewallPid(int rootpid, const QString& state) {
|
||||
ProcessGroup* group = m_pidtracker->group(rootpid);
|
||||
if (!group) {
|
||||
return false;
|
||||
}
|
||||
|
||||
group->state = state;
|
||||
group->moveToCgroup(getAppStateCgroup(group->state));
|
||||
|
||||
logger.debug() << "Setting" << group->name << "PID:" << rootpid
|
||||
<< "to firewall state" << state;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Clear the firewall and return all applications to the active state */
|
||||
bool DBusService::firewallClear() {
|
||||
const QString cgroup = getAppStateCgroup(APP_STATE_ACTIVE);
|
||||
|
||||
m_firewallApps.clear();
|
||||
for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) {
|
||||
ProcessGroup* group = *i;
|
||||
if (group->state == APP_STATE_ACTIVE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
group->state = APP_STATE_ACTIVE;
|
||||
group->moveToCgroup(cgroup);
|
||||
|
||||
logger.debug() << "Setting" << group->name << "PID:" << group->rootpid
|
||||
<< "to firewall state" << group->state;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DBusService::getAppStateCgroup(const QString& state) {
|
||||
if (state == APP_STATE_EXCLUDED) {
|
||||
return m_wgutils->getExcludeCgroup();
|
||||
}
|
||||
if (state == APP_STATE_BLOCKED) {
|
||||
return m_wgutils->getBlockCgroup();
|
||||
}
|
||||
return m_wgutils->getDefaultCgroup();
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DBUSSERVICE_H
|
||||
#define DBUSSERVICE_H
|
||||
|
||||
#include "daemon/daemon.h"
|
||||
#include "apptracker.h"
|
||||
#include "iputilslinux.h"
|
||||
#include "dnsutilslinux.h"
|
||||
#include "pidtracker.h"
|
||||
#include "wireguardutilslinux.h"
|
||||
|
||||
class DbusAdaptor;
|
||||
|
||||
class DBusService final : public Daemon {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DBusService)
|
||||
Q_CLASSINFO("D-Bus Interface", "org.mozilla.vpn.dbus")
|
||||
|
||||
public:
|
||||
DBusService(QObject* parent);
|
||||
~DBusService();
|
||||
|
||||
void setAdaptor(DbusAdaptor* adaptor);
|
||||
|
||||
using Daemon::activate;
|
||||
|
||||
public slots:
|
||||
bool activate(const QString& jsonConfig);
|
||||
|
||||
bool deactivate(bool emitSignals = true) override;
|
||||
QString status();
|
||||
|
||||
QString version();
|
||||
QString getLogs();
|
||||
|
||||
QString runningApps();
|
||||
bool firewallApp(const QString& appName, const QString& state);
|
||||
bool firewallPid(int rootpid, const QString& state);
|
||||
bool firewallClear();
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
bool supportIPUtils() const override { return true; }
|
||||
IPUtils* iputils() override;
|
||||
bool supportDnsUtils() const override { return true; }
|
||||
DnsUtils* dnsutils() override;
|
||||
|
||||
QByteArray getStatus() override;
|
||||
|
||||
private:
|
||||
bool removeInterfaceIfExists();
|
||||
QString getAppStateCgroup(const QString& state);
|
||||
|
||||
private slots:
|
||||
void appLaunched(const QString& name, int rootpid);
|
||||
void appTerminated(const QString& name, int rootpid);
|
||||
|
||||
private:
|
||||
DbusAdaptor* m_adaptor = nullptr;
|
||||
WireguardUtilsLinux* m_wgutils = nullptr;
|
||||
IPUtilsLinux* m_iputils = nullptr;
|
||||
DnsUtilsLinux* m_dnsutils = nullptr;
|
||||
|
||||
AppTracker* m_apptracker = nullptr;
|
||||
PidTracker* m_pidtracker = nullptr;
|
||||
QMap<QString, QString> m_firewallApps;
|
||||
};
|
||||
|
||||
#endif // DBUSSERVICE_H
|
|
@ -1,170 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DBUSTYPESLINUX_H
|
||||
#define DBUSTYPESLINUX_H
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
#include <QDBusArgument>
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
/* 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
|
|
@ -1,206 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "dnsutilslinux.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
#include <QDBusVariant>
|
||||
|
||||
#include <net/if.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(LOG_LINUX, "DnsUtilsLinux");
|
||||
}
|
||||
|
||||
DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
|
||||
MVPN_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() {
|
||||
MVPN_COUNT_DTOR(DnsUtilsLinux);
|
||||
|
||||
for (int ifindex : m_linkDomains.keys()) {
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(ifindex);
|
||||
argumentList << QVariant::fromValue(m_linkDomains[ifindex]);
|
||||
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 ifindex : m_linkDomains.keys()) {
|
||||
setLinkDomains(ifindex, m_linkDomains[ifindex]);
|
||||
}
|
||||
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 (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 (auto d : domains) {
|
||||
logger.debug() << "Setting DNS domain:" << 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 (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 ifindex : m_linkDomains.keys()) {
|
||||
if (!m_linkDomains[ifindex].contains(root)) {
|
||||
continue;
|
||||
}
|
||||
QList<DnsLinkDomain> newlist = m_linkDomains[ifindex];
|
||||
newlist.removeAll(root);
|
||||
setLinkDomains(ifindex, newlist);
|
||||
}
|
||||
|
||||
/* Add a root search domain for the new interface. */
|
||||
QList<DnsLinkDomain> newlist = {root};
|
||||
setLinkDomains(m_ifindex, newlist);
|
||||
delete call;
|
||||
}
|
||||
|
||||
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;
|
|
@ -1,41 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DNSUTILSLINUX_H
|
||||
#define DNSUTILSLINUX_H
|
||||
|
||||
#include "daemon/dnsutils.h"
|
||||
#include "dbustypeslinux.h"
|
||||
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
|
||||
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
|
|
@ -1,150 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "iputilslinux.h"
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QScopeGuard>
|
||||
|
||||
constexpr uint32_t ETH_MTU = 1500;
|
||||
constexpr uint32_t WG_MTU_OVERHEAD = 80;
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "IPUtilsLinux");
|
||||
}
|
||||
|
||||
IPUtilsLinux::IPUtilsLinux(QObject* parent) : IPUtils(parent) {
|
||||
MVPN_COUNT_CTOR(IPUtilsLinux);
|
||||
logger.debug() << "IPUtilsLinux created.";
|
||||
}
|
||||
|
||||
IPUtilsLinux::~IPUtilsLinux() {
|
||||
MVPN_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: " << 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: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IPUTILSLINUX_H
|
||||
#define IPUTILSLINUX_H
|
||||
|
||||
#include "daemon/iputils.h"
|
||||
|
||||
#include <arpa/inet.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
|
|
@ -1,58 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "command.h"
|
||||
#include "dbusservice.h"
|
||||
#include "dbus_adaptor.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "loghandler.h"
|
||||
#include "signalhandler.h"
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "main");
|
||||
}
|
||||
|
||||
class CommandLinuxDaemon final : public Command {
|
||||
public:
|
||||
explicit CommandLinuxDaemon(QObject* parent)
|
||||
: Command(parent, "linuxdaemon", "Starts the linux daemon") {
|
||||
MVPN_COUNT_CTOR(CommandLinuxDaemon);
|
||||
}
|
||||
|
||||
~CommandLinuxDaemon() { MVPN_COUNT_DTOR(CommandLinuxDaemon); }
|
||||
|
||||
int run(QStringList& tokens) override {
|
||||
Q_ASSERT(!tokens.isEmpty());
|
||||
LogHandler::setLocation("/var/log");
|
||||
|
||||
return runCommandLineApp([&]() {
|
||||
DBusService* dbus = new DBusService(qApp);
|
||||
DbusAdaptor* adaptor = new DbusAdaptor(dbus);
|
||||
dbus->setAdaptor(adaptor);
|
||||
|
||||
QDBusConnection connection = QDBusConnection::systemBus();
|
||||
logger.debug() << "Connecting to DBus...";
|
||||
|
||||
if (!connection.registerService("org.mozilla.vpn.dbus") ||
|
||||
!connection.registerObject("/", dbus)) {
|
||||
logger.error() << "Connection failed - name:"
|
||||
<< connection.lastError().name()
|
||||
<< "message:" << connection.lastError().message();
|
||||
return 1;
|
||||
}
|
||||
|
||||
SignalHandler sh;
|
||||
QObject::connect(&sh, &SignalHandler::quitRequested, [&]() {
|
||||
dbus->deactivate();
|
||||
qApp->quit();
|
||||
});
|
||||
|
||||
logger.debug() << "Ready!";
|
||||
return qApp->exec();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static Command::RegistrationProxy<CommandLinuxDaemon> s_commandLinuxDaemon;
|
|
@ -1,28 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
|
||||
|
||||
<!DOCTYPE busconfig PUBLIC
|
||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
|
||||
<!-- Only root can own the service -->
|
||||
<policy user="root">
|
||||
<allow own="org.mozilla.vpn.dbus"/>
|
||||
</policy>
|
||||
|
||||
|
||||
<!-- Allow anyone to invoke methods on the interfaces -->
|
||||
<policy context="default">
|
||||
<allow own="org.mozilla.vpn.dbus"/>
|
||||
<allow send_destination="org.mozilla.vpn.dbus"/>
|
||||
|
||||
<allow send_destination="org.mozilla.vpn.dbus"
|
||||
send_interface="org.freedesktop.DBus.Introspectable"/>
|
||||
<allow send_destination="org.mozilla.vpn.dbus"
|
||||
send_interface="org.freedesktop.DBus.Properties"/>
|
||||
<allow send_destination="org.mozilla.vpn.dbus"
|
||||
send_interface="org.freedesktop.DBus.Properties"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[D-BUS Service]
|
||||
User=root
|
||||
Name=org.mozilla.vpn.dbus
|
||||
Exec=/usr/bin/mozillavpn linuxdaemon
|
||||
SystemdService=mozillavpn.service
|
|
@ -1,46 +0,0 @@
|
|||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.mozilla.vpn.dbus">
|
||||
<method name="version">
|
||||
<arg type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="activate">
|
||||
<arg type="b" direction="out"/>
|
||||
<arg name="jsonConfig" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="deactivate">
|
||||
<arg type="b" direction="out"/>
|
||||
</method>
|
||||
<method name="status">
|
||||
<arg name="jsonStatus" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="runningApps">
|
||||
<arg type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="firewallApp">
|
||||
<arg type="b" direction="out"/>
|
||||
<arg name="appName" type="s" direction="in"/>
|
||||
<arg name="state" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="firewallPid">
|
||||
<arg type="b" direction="out"/>
|
||||
<arg name="rootpid" type="i" direction="in"/>
|
||||
<arg name="state" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="firewallClear">
|
||||
<arg type="b" direction="out"/>
|
||||
</method>
|
||||
<method name="getLogs">
|
||||
<arg name="logs" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="cleanupLogs">
|
||||
</method>
|
||||
<signal name="connected">
|
||||
<arg name="hopindex" type="i" direction="out"/>
|
||||
</signal>
|
||||
<signal name="disconnected">
|
||||
<arg name="hopindex" type="i" direction="out"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
|
||||
<action id="org.mozilla.vpn.activate">
|
||||
<description>Activate the Mozilla VPN</description>
|
||||
<message>Activate the Mozilla VPN</message>
|
||||
<defaults>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>auth_admin</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.mozilla.vpn.deactivate">
|
||||
<description>Deactivate the Mozilla VPN</description>
|
||||
<message>Deactivate the Mozilla VPN</message>
|
||||
<defaults>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>auth_admin</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "pidtracker.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/connector.h>
|
||||
#include <linux/cn_proc.h>
|
||||
|
||||
constexpr size_t CN_MCAST_MSG_SIZE =
|
||||
sizeof(struct cn_msg) + sizeof(enum proc_cn_mcast_op);
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "PidTracker");
|
||||
}
|
||||
|
||||
PidTracker::PidTracker(QObject* parent) : QObject(parent) {
|
||||
MVPN_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() {
|
||||
MVPN_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 pid : kthreads.keys()) {
|
||||
fprintf(fp, "%d\n", pid);
|
||||
fflush(fp);
|
||||
}
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef 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") {
|
||||
MVPN_COUNT_CTOR(ProcessGroup);
|
||||
name = groupName;
|
||||
rootpid = groupRootPid;
|
||||
state = groupState;
|
||||
refcount = 0;
|
||||
}
|
||||
~ProcessGroup() { MVPN_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
|
|
@ -1,66 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "polkithelper.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
// No extra QT includes after this line!
|
||||
#undef Q_SIGNALS
|
||||
#include "polkit/polkit.h"
|
||||
|
||||
class Helper final {
|
||||
public:
|
||||
Helper() = default;
|
||||
~Helper() {
|
||||
if (m_error) {
|
||||
g_error_free(m_error);
|
||||
}
|
||||
|
||||
if (m_subject) {
|
||||
g_object_unref(m_subject);
|
||||
}
|
||||
|
||||
if (m_result) {
|
||||
g_object_unref(m_result);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
GError* m_error = nullptr;
|
||||
PolkitSubject* m_subject = nullptr;
|
||||
PolkitAuthorizationResult* m_result = nullptr;
|
||||
};
|
||||
|
||||
// static
|
||||
PolkitHelper* PolkitHelper::instance() {
|
||||
static PolkitHelper s_instance;
|
||||
return &s_instance;
|
||||
}
|
||||
|
||||
bool PolkitHelper::checkAuthorization(const QString& actionId) {
|
||||
qDebug() << "Check Authorization for" << actionId;
|
||||
|
||||
Helper h;
|
||||
|
||||
PolkitAuthority* authority = polkit_authority_get_sync(NULL, &h.m_error);
|
||||
if (h.m_error) {
|
||||
qDebug() << "Fail to generate a polkit authority object:"
|
||||
<< h.m_error->message;
|
||||
return false;
|
||||
}
|
||||
|
||||
h.m_subject = polkit_unix_process_new_for_owner(getpid(), 0, -1);
|
||||
|
||||
h.m_result = polkit_authority_check_authorization_sync(
|
||||
authority, h.m_subject, actionId.toLatin1().data(), nullptr,
|
||||
POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, nullptr,
|
||||
&h.m_error);
|
||||
if (h.m_error) {
|
||||
qDebug() << "Authorization sync failed:" << h.m_error->message;
|
||||
return false;
|
||||
}
|
||||
|
||||
return polkit_authorization_result_get_is_authorized(h.m_result);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef POLKITHELPER_H
|
||||
#define POLKITHELPER_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class PolkitHelper final {
|
||||
public:
|
||||
static PolkitHelper* instance();
|
||||
|
||||
bool checkAuthorization(const QString& actionId);
|
||||
|
||||
private:
|
||||
PolkitHelper() = default;
|
||||
~PolkitHelper() = default;
|
||||
|
||||
Q_DISABLE_COPY(PolkitHelper)
|
||||
};
|
||||
|
||||
#endif // POLKITHELPER_H
|
|
@ -1,648 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "wireguardutilslinux.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "platforms/linux/linuxdependencies.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/fib_rules.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <mntent.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Import wireguard C library for Linux
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
#include "../../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.h"
|
||||
#include "../../linux/netfilter/netfilter.h"
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
// End import wireguard
|
||||
|
||||
/* Packets sent outside the VPN need to be marked for the routing policy
|
||||
* to direct them appropriately. The value of the mark and the table ID
|
||||
* aren't important, so long as they are unique.
|
||||
*/
|
||||
constexpr uint32_t WG_FIREWALL_MARK = 0xca6c;
|
||||
constexpr uint32_t WG_ROUTE_TABLE = 0xca6c;
|
||||
|
||||
/* Traffic classifiers can be used to mark packets which should be either
|
||||
* excluded from the VPN tunnel, or blocked entirely. The values of these
|
||||
* classifiers aren't important so long as they are unique.
|
||||
*/
|
||||
constexpr const char* VPN_EXCLUDE_CGROUP = "/mozvpn.exclude";
|
||||
constexpr const char* VPN_BLOCK_CGROUP = "/mozvpn.block";
|
||||
constexpr uint32_t VPN_EXCLUDE_CLASS_ID = 0x00110011;
|
||||
constexpr uint32_t VPN_BLOCK_CLASS_ID = 0x00220022;
|
||||
|
||||
static void nlmsg_append_attr(char* buf, size_t maxlen, int attrtype,
|
||||
const void* attrdata, size_t attrlen);
|
||||
static void nlmsg_append_attr32(char* buf, size_t maxlen, int attrtype,
|
||||
uint32_t value);
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "WireguardUtilsLinux");
|
||||
|
||||
void NetfilterLogger(int level, const char* msg) {
|
||||
Q_UNUSED(level);
|
||||
logger.debug() << "NetfilterGo:" << msg;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
WireguardUtilsLinux::WireguardUtilsLinux(QObject* parent)
|
||||
: WireguardUtils(parent) {
|
||||
MVPN_COUNT_CTOR(WireguardUtilsLinux);
|
||||
NetfilterSetLogger((GoUintptr)&NetfilterLogger);
|
||||
NetfilterCreateTables();
|
||||
|
||||
m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||
if (m_nlsock < 0) {
|
||||
logger.warning() << "Failed to create netlink socket:" << strerror(errno);
|
||||
}
|
||||
|
||||
struct sockaddr_nl nladdr;
|
||||
memset(&nladdr, 0, sizeof(nladdr));
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
nladdr.nl_pid = getpid();
|
||||
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) {
|
||||
logger.warning() << "Failed to bind netlink socket:" << strerror(errno);
|
||||
}
|
||||
|
||||
m_notifier = new QSocketNotifier(m_nlsock, QSocketNotifier::Read, this);
|
||||
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||
&WireguardUtilsLinux::nlsockReady);
|
||||
|
||||
/* Create control groups for split tunnelling */
|
||||
m_cgroups = LinuxDependencies::findCgroupPath("net_cls");
|
||||
if (!m_cgroups.isNull()) {
|
||||
if (!setupCgroupClass(m_cgroups + VPN_EXCLUDE_CGROUP,
|
||||
VPN_EXCLUDE_CLASS_ID)) {
|
||||
m_cgroups.clear();
|
||||
} else if (!setupCgroupClass(m_cgroups + VPN_BLOCK_CGROUP,
|
||||
VPN_BLOCK_CLASS_ID)) {
|
||||
m_cgroups.clear();
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug() << "WireguardUtilsLinux created.";
|
||||
}
|
||||
|
||||
WireguardUtilsLinux::~WireguardUtilsLinux() {
|
||||
MVPN_COUNT_DTOR(WireguardUtilsLinux);
|
||||
NetfilterRemoveTables();
|
||||
if (m_nlsock >= 0) {
|
||||
close(m_nlsock);
|
||||
}
|
||||
logger.debug() << "WireguardUtilsLinux destroyed.";
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::interfaceExists() {
|
||||
// As currentInterfaces only gets wireguard interfaces, this method
|
||||
// also confirms an interface as being a wireguard interface.
|
||||
return currentInterfaces().contains(WG_INTERFACE);
|
||||
};
|
||||
|
||||
bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||
int code = wg_add_device(WG_INTERFACE);
|
||||
if (code != 0) {
|
||||
logger.error() << "Adding interface failed:" << strerror(-code);
|
||||
return false;
|
||||
}
|
||||
|
||||
wg_device* device = static_cast<wg_device*>(calloc(1, sizeof(*device)));
|
||||
if (!device) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { wg_free_device(device); });
|
||||
|
||||
// Name
|
||||
strncpy(device->name, WG_INTERFACE, IFNAMSIZ);
|
||||
// Private Key
|
||||
wg_key_from_base64(device->private_key, config.m_privateKey.toLocal8Bit());
|
||||
|
||||
// Set/update device
|
||||
device->fwmark = WG_FIREWALL_MARK;
|
||||
device->flags = (wg_device_flags)(
|
||||
WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_REPLACE_PEERS | WGDEVICE_HAS_FWMARK);
|
||||
if (wg_set_device(device) != 0) {
|
||||
logger.error() << "Failed to setup the device";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create routing policy rules
|
||||
if (!rtmSendRule(RTM_NEWRULE,
|
||||
NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK,
|
||||
AF_INET)) {
|
||||
return false;
|
||||
}
|
||||
if (!rtmSendRule(RTM_NEWRULE,
|
||||
NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK,
|
||||
AF_INET6)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure firewall rules
|
||||
GoString goIfname = {.p = device->name, .n = (ptrdiff_t)strlen(device->name)};
|
||||
if (NetfilterIfup(goIfname, device->fwmark) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (!m_cgroups.isNull()) {
|
||||
NetfilterMarkCgroup(VPN_EXCLUDE_CLASS_ID, device->fwmark);
|
||||
NetfilterBlockCgroup(VPN_BLOCK_CLASS_ID);
|
||||
}
|
||||
|
||||
int slashPos = config.m_deviceIpv6Address.indexOf('/');
|
||||
GoString goIpv6Address = {.p = qPrintable(config.m_deviceIpv6Address),
|
||||
.n = config.m_deviceIpv6Address.length()};
|
||||
if (slashPos != -1) {
|
||||
goIpv6Address.n = slashPos;
|
||||
}
|
||||
NetfilterIsolateIpv6(goIfname, goIpv6Address);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||
wg_device* device = static_cast<wg_device*>(calloc(1, sizeof(*device)));
|
||||
if (!device) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { wg_free_device(device); });
|
||||
|
||||
wg_peer* peer = static_cast<wg_peer*>(calloc(1, sizeof(*peer)));
|
||||
if (!peer) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
device->first_peer = device->last_peer = peer;
|
||||
|
||||
logger.debug() << "Adding peer" << printablePubkey(config.m_serverPublicKey);
|
||||
|
||||
// Public Key
|
||||
wg_key_from_base64(peer->public_key, qPrintable(config.m_serverPublicKey));
|
||||
// Endpoint
|
||||
if (!setPeerEndpoint(&peer->endpoint.addr, config.m_serverIpv4AddrIn,
|
||||
config.m_serverPort)) {
|
||||
logger.error() << "Failed to set peer endpoint for hop"
|
||||
<< config.m_hopindex;
|
||||
return false;
|
||||
}
|
||||
|
||||
// HACK: We are running into a crash on Linux due to the address list being
|
||||
// *WAAAY* too long, which we aren't really using anways since the routing
|
||||
// tables are doing all the work for us anyways.
|
||||
//
|
||||
// To work around the issue, just set default routes for hopindex zero.
|
||||
if (config.m_hopindex == 0) {
|
||||
if (!config.m_deviceIpv4Address.isNull()) {
|
||||
addPeerPrefix(peer, IPAddressRange("0.0.0.0", 0, IPAddressRange::IPv4));
|
||||
}
|
||||
if (!config.m_deviceIpv6Address.isNull()) {
|
||||
addPeerPrefix(peer, IPAddressRange("::", 0, IPAddressRange::IPv6));
|
||||
}
|
||||
} else {
|
||||
for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) {
|
||||
bool ok = addPeerPrefix(peer, ip);
|
||||
if (!ok) {
|
||||
logger.error() << "Invalid IP address:" << ip.ipAddress();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set/update peer
|
||||
strncpy(device->name, WG_INTERFACE, IFNAMSIZ);
|
||||
device->flags = (wg_device_flags)0;
|
||||
peer->flags =
|
||||
(wg_peer_flags)(WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS);
|
||||
if (wg_set_device(device) != 0) {
|
||||
logger.error() << "Failed to set the new peer hop" << config.m_hopindex;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::deletePeer(const QString& pubkey) {
|
||||
wg_device* device = static_cast<wg_device*>(calloc(1, sizeof(*device)));
|
||||
if (!device) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { wg_free_device(device); });
|
||||
|
||||
wg_peer* peer = static_cast<wg_peer*>(calloc(1, sizeof(*peer)));
|
||||
if (!peer) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
device->first_peer = device->last_peer = peer;
|
||||
|
||||
logger.debug() << "Removing peer" << printablePubkey(pubkey);
|
||||
|
||||
// Public Key
|
||||
peer->flags = (wg_peer_flags)(WGPEER_HAS_PUBLIC_KEY | WGPEER_REMOVE_ME);
|
||||
wg_key_from_base64(peer->public_key, qPrintable(pubkey));
|
||||
|
||||
// Set/update device
|
||||
strncpy(device->name, WG_INTERFACE, IFNAMSIZ);
|
||||
device->flags = (wg_device_flags)0;
|
||||
if (wg_set_device(device) != 0) {
|
||||
logger.error() << "Failed to remove the peer";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::deleteInterface() {
|
||||
// Clear firewall rules
|
||||
NetfilterClearTables();
|
||||
|
||||
// Clear routing policy rules
|
||||
if (!rtmSendRule(RTM_DELRULE, NLM_F_REQUEST | NLM_F_ACK, AF_INET)) {
|
||||
return false;
|
||||
}
|
||||
if (!rtmSendRule(RTM_DELRULE, NLM_F_REQUEST | NLM_F_ACK, AF_INET6)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the interface
|
||||
int returnCode = wg_del_device(WG_INTERFACE);
|
||||
if (returnCode != 0) {
|
||||
logger.error() << "Deleting interface failed:" << strerror(-returnCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WireguardUtils::peerStatus WireguardUtilsLinux::getPeerStatus(
|
||||
const QString& pubkey) {
|
||||
wg_device* device = nullptr;
|
||||
wg_peer* peer = nullptr;
|
||||
peerStatus status = {0, 0};
|
||||
|
||||
if (wg_get_device(&device, WG_INTERFACE) != 0) {
|
||||
logger.warning() << "Unable to get stats for" << WG_INTERFACE;
|
||||
return status;
|
||||
}
|
||||
|
||||
wg_key key;
|
||||
wg_key_from_base64(key, qPrintable(pubkey));
|
||||
wg_for_each_peer(device, peer) {
|
||||
if (memcmp(&key, &peer->public_key, sizeof(key)) != 0) {
|
||||
continue;
|
||||
}
|
||||
status.txBytes = peer->tx_bytes;
|
||||
status.rxBytes = peer->rx_bytes;
|
||||
break;
|
||||
}
|
||||
wg_free_device(device);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddressRange& prefix,
|
||||
int hopindex) {
|
||||
logger.debug() << "Adding route to" << prefix.toString();
|
||||
int flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
|
||||
return rtmSendRoute(RTM_NEWROUTE, flags, prefix, hopindex);
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::deleteRoutePrefix(const IPAddressRange& prefix,
|
||||
int hopindex) {
|
||||
logger.debug() << "Removing route to" << prefix.toString();
|
||||
int flags = NLM_F_REQUEST | NLM_F_ACK;
|
||||
return rtmSendRoute(RTM_DELROUTE, flags, prefix, hopindex);
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::rtmSendRoute(int action, int flags,
|
||||
const IPAddressRange& prefix,
|
||||
int hopindex) {
|
||||
constexpr size_t rtm_max_size = sizeof(struct rtmsg) +
|
||||
2 * RTA_SPACE(sizeof(uint32_t)) +
|
||||
RTA_SPACE(sizeof(struct in6_addr));
|
||||
int index = if_nametoindex(WG_INTERFACE);
|
||||
if (index <= 0) {
|
||||
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
wg_allowedip ip;
|
||||
if (!buildAllowedIp(&ip, prefix)) {
|
||||
logger.warning() << "Invalid destination prefix";
|
||||
return false;
|
||||
}
|
||||
|
||||
char buf[NLMSG_SPACE(rtm_max_size)];
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
struct rtmsg* rtm = (struct rtmsg*)NLMSG_DATA(nlmsg);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||
nlmsg->nlmsg_type = action;
|
||||
nlmsg->nlmsg_flags = flags;
|
||||
nlmsg->nlmsg_pid = getpid();
|
||||
nlmsg->nlmsg_seq = m_nlseq++;
|
||||
rtm->rtm_dst_len = ip.cidr;
|
||||
rtm->rtm_family = ip.family;
|
||||
rtm->rtm_type = RTN_UNICAST;
|
||||
rtm->rtm_protocol = RTPROT_BOOT;
|
||||
rtm->rtm_scope = RT_SCOPE_UNIVERSE;
|
||||
|
||||
// Routes for the main hop should be placed into their own table.
|
||||
if (hopindex == 0) {
|
||||
rtm->rtm_table = RT_TABLE_UNSPEC;
|
||||
nlmsg_append_attr32(buf, sizeof(buf), RTA_TABLE, WG_ROUTE_TABLE);
|
||||
} else {
|
||||
rtm->rtm_table = RT_TABLE_MAIN;
|
||||
}
|
||||
|
||||
if (rtm->rtm_family == AF_INET6) {
|
||||
nlmsg_append_attr(buf, sizeof(buf), RTA_DST, &ip.ip6, sizeof(ip.ip6));
|
||||
} else {
|
||||
nlmsg_append_attr(buf, sizeof(buf), RTA_DST, &ip.ip4, sizeof(ip.ip4));
|
||||
}
|
||||
nlmsg_append_attr32(buf, sizeof(buf), RTA_OIF, index);
|
||||
|
||||
struct sockaddr_nl nladdr;
|
||||
memset(&nladdr, 0, sizeof(nladdr));
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
size_t result = sendto(m_nlsock, buf, nlmsg->nlmsg_len, 0,
|
||||
(struct sockaddr*)&nladdr, sizeof(nladdr));
|
||||
return (result == nlmsg->nlmsg_len);
|
||||
}
|
||||
|
||||
// PRIVATE METHODS
|
||||
QStringList WireguardUtilsLinux::currentInterfaces() {
|
||||
char* deviceNames = wg_list_device_names();
|
||||
QStringList devices;
|
||||
if (!deviceNames) {
|
||||
return devices;
|
||||
}
|
||||
char* deviceName;
|
||||
size_t len;
|
||||
wg_for_each_device_name(deviceNames, deviceName, len) {
|
||||
devices.append(deviceName);
|
||||
}
|
||||
free(deviceNames);
|
||||
return devices;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::setPeerEndpoint(struct sockaddr* sa,
|
||||
const QString& address, int port) {
|
||||
QString portString = QString::number(port);
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
|
||||
struct addrinfo* resolved = nullptr;
|
||||
auto guard = qScopeGuard([&] { freeaddrinfo(resolved); });
|
||||
int retries = 15;
|
||||
|
||||
for (unsigned int timeout = 1000000;;
|
||||
timeout = std::min((unsigned int)20000000, timeout * 6 / 5)) {
|
||||
int rv = getaddrinfo(address.toLocal8Bit(), portString.toLocal8Bit(),
|
||||
&hints, &resolved);
|
||||
if (!rv) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* The set of return codes that are "permanent failures". All other
|
||||
* possibilities are potentially transient.
|
||||
*
|
||||
* This is according to https://sourceware.org/glibc/wiki/NameResolver which
|
||||
* states: "From the perspective of the application that calls getaddrinfo()
|
||||
* it perhaps doesn't matter that much since EAI_FAIL, EAI_NONAME and
|
||||
* EAI_NODATA are all permanent failure codes and the causes are all
|
||||
* permanent failures in the sense that there is no point in retrying
|
||||
* later."
|
||||
*
|
||||
* So this is what we do, except FreeBSD removed EAI_NODATA some time ago,
|
||||
* so that's conditional.
|
||||
*/
|
||||
if (rv == EAI_NONAME || rv == EAI_FAIL ||
|
||||
#ifdef EAI_NODATA
|
||||
rv == EAI_NODATA ||
|
||||
#endif
|
||||
(retries >= 0 && !retries--)) {
|
||||
logger.error() << "Failed to resolve the address endpoint";
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.warning() << "Trying again in" << (timeout / 1000000.0) << "seconds";
|
||||
usleep(timeout);
|
||||
}
|
||||
|
||||
if ((resolved->ai_family == AF_INET &&
|
||||
resolved->ai_addrlen == sizeof(struct sockaddr_in)) ||
|
||||
(resolved->ai_family == AF_INET6 &&
|
||||
resolved->ai_addrlen == sizeof(struct sockaddr_in6))) {
|
||||
memcpy(sa, resolved->ai_addr, resolved->ai_addrlen);
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.error() << "Invalid endpoint" << address;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::addPeerPrefix(wg_peer* peer,
|
||||
const IPAddressRange& prefix) {
|
||||
Q_ASSERT(peer);
|
||||
|
||||
wg_allowedip* allowedip =
|
||||
static_cast<wg_allowedip*>(calloc(1, sizeof(*allowedip)));
|
||||
if (!allowedip) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!peer->first_allowedip) {
|
||||
peer->first_allowedip = allowedip;
|
||||
} else {
|
||||
peer->last_allowedip->next_allowedip = allowedip;
|
||||
}
|
||||
peer->last_allowedip = allowedip;
|
||||
|
||||
return buildAllowedIp(allowedip, prefix);
|
||||
}
|
||||
|
||||
static void nlmsg_append_attr(char* buf, size_t maxlen, int attrtype,
|
||||
const void* attrdata, size_t attrlen) {
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
size_t newlen = NLMSG_ALIGN(nlmsg->nlmsg_len) + RTA_SPACE(attrlen);
|
||||
if (newlen <= maxlen) {
|
||||
struct rtattr* attr = (struct rtattr*)(buf + NLMSG_ALIGN(nlmsg->nlmsg_len));
|
||||
attr->rta_type = attrtype;
|
||||
attr->rta_len = RTA_LENGTH(attrlen);
|
||||
memcpy(RTA_DATA(attr), attrdata, attrlen);
|
||||
nlmsg->nlmsg_len = newlen;
|
||||
}
|
||||
}
|
||||
|
||||
static void nlmsg_append_attr32(char* buf, size_t maxlen, int attrtype,
|
||||
uint32_t value) {
|
||||
nlmsg_append_attr(buf, maxlen, attrtype, &value, sizeof(value));
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::rtmSendRule(int action, int flags, int addrfamily) {
|
||||
constexpr size_t fib_max_size =
|
||||
sizeof(struct fib_rule_hdr) + 2 * RTA_SPACE(sizeof(uint32_t));
|
||||
|
||||
char buf[NLMSG_SPACE(fib_max_size)];
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
struct fib_rule_hdr* rule = (struct fib_rule_hdr*)NLMSG_DATA(nlmsg);
|
||||
struct sockaddr_nl nladdr;
|
||||
memset(&nladdr, 0, sizeof(nladdr));
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
|
||||
/* Create a routing policy rule to select the wireguard routing table for
|
||||
* unmarked packets. This is equivalent to:
|
||||
* ip rule add not fwmark $WG_FIREWALL_MARK table $WG_ROUTE_TABLE
|
||||
*/
|
||||
memset(buf, 0, sizeof(buf));
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct fib_rule_hdr));
|
||||
nlmsg->nlmsg_type = action;
|
||||
nlmsg->nlmsg_flags = flags;
|
||||
nlmsg->nlmsg_pid = getpid();
|
||||
nlmsg->nlmsg_seq = m_nlseq++;
|
||||
rule->family = addrfamily;
|
||||
rule->table = RT_TABLE_UNSPEC;
|
||||
rule->action = FR_ACT_TO_TBL;
|
||||
rule->flags = FIB_RULE_INVERT;
|
||||
nlmsg_append_attr32(buf, sizeof(buf), FRA_FWMARK, WG_FIREWALL_MARK);
|
||||
nlmsg_append_attr32(buf, sizeof(buf), FRA_TABLE, WG_ROUTE_TABLE);
|
||||
ssize_t result = sendto(m_nlsock, buf, nlmsg->nlmsg_len, 0,
|
||||
(struct sockaddr*)&nladdr, sizeof(nladdr));
|
||||
if (result != nlmsg->nlmsg_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Create a routing policy rule to suppress zero-length prefix lookups from
|
||||
* in the main routing table. This is equivalent to:
|
||||
* ip rule add table main suppress_prefixlength 0
|
||||
*/
|
||||
memset(buf, 0, sizeof(buf));
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct fib_rule_hdr));
|
||||
nlmsg->nlmsg_type = action;
|
||||
nlmsg->nlmsg_flags = flags;
|
||||
nlmsg->nlmsg_pid = getpid();
|
||||
nlmsg->nlmsg_seq = m_nlseq++;
|
||||
rule->family = addrfamily;
|
||||
rule->table = RT_TABLE_MAIN;
|
||||
rule->action = FR_ACT_TO_TBL;
|
||||
rule->flags = 0;
|
||||
nlmsg_append_attr32(buf, sizeof(buf), FRA_SUPPRESS_PREFIXLEN, 0);
|
||||
result = sendto(m_nlsock, buf, nlmsg->nlmsg_len, 0, (struct sockaddr*)&nladdr,
|
||||
sizeof(nladdr));
|
||||
if (result != nlmsg->nlmsg_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WireguardUtilsLinux::nlsockReady() {
|
||||
char buf[1024];
|
||||
ssize_t len = recv(m_nlsock, buf, sizeof(buf), MSG_DONTWAIT);
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
while (NLMSG_OK(nlmsg, len)) {
|
||||
if (nlmsg->nlmsg_type == NLMSG_DONE) {
|
||||
return;
|
||||
}
|
||||
if (nlmsg->nlmsg_type != NLMSG_ERROR) {
|
||||
nlmsg = NLMSG_NEXT(nlmsg, len);
|
||||
continue;
|
||||
}
|
||||
struct nlmsgerr* err = (struct nlmsgerr*)NLMSG_DATA(nlmsg);
|
||||
if (err->error != 0) {
|
||||
logger.debug() << "Netlink request failed:" << strerror(-err->error);
|
||||
}
|
||||
nlmsg = NLMSG_NEXT(nlmsg, len);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
bool WireguardUtilsLinux::setupCgroupClass(const QString& path,
|
||||
unsigned long classid) {
|
||||
logger.debug() << "Creating control group:" << path;
|
||||
int flags = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
|
||||
int err = mkdir(qPrintable(path), flags);
|
||||
if ((err < 0) && (errno != EEXIST)) {
|
||||
logger.error() << "Failed to create" << path + ":" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString netClassPath = path + "/net_cls.classid";
|
||||
FILE* fp = fopen(qPrintable(netClassPath), "w");
|
||||
if (!fp) {
|
||||
logger.error() << "Failed to set classid:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
fprintf(fp, "%lu", classid);
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString WireguardUtilsLinux::getExcludeCgroup() const {
|
||||
if (m_cgroups.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
return m_cgroups + VPN_EXCLUDE_CGROUP;
|
||||
}
|
||||
|
||||
QString WireguardUtilsLinux::getBlockCgroup() const {
|
||||
if (m_cgroups.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
return m_cgroups + VPN_BLOCK_CGROUP;
|
||||
}
|
||||
|
||||
// static
|
||||
bool WireguardUtilsLinux::buildAllowedIp(wg_allowedip* ip,
|
||||
const IPAddressRange& prefix) {
|
||||
if (prefix.type() == IPAddressRange::IPv4) {
|
||||
ip->family = AF_INET;
|
||||
ip->cidr = prefix.range();
|
||||
return inet_pton(AF_INET, qPrintable(prefix.ipAddress()), &ip->ip4) == 1;
|
||||
}
|
||||
if (prefix.type() == IPAddressRange::IPv6) {
|
||||
ip->family = AF_INET6;
|
||||
ip->cidr = prefix.range();
|
||||
return inet_pton(AF_INET6, qPrintable(prefix.ipAddress()), &ip->ip6) == 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
QString WireguardUtilsLinux::printablePubkey(const QString& pubkey) {
|
||||
if (pubkey.length() < 12) {
|
||||
return pubkey;
|
||||
} else {
|
||||
return pubkey.left(6) + "..." + pubkey.right(6);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WIREGUARDUTILSLINUX_H
|
||||
#define WIREGUARDUTILSLINUX_H
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include <QObject>
|
||||
#include <QSocketNotifier>
|
||||
#include <QStringList>
|
||||
|
||||
class WireguardUtilsLinux final : public WireguardUtils {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WireguardUtilsLinux(QObject* parent);
|
||||
~WireguardUtilsLinux();
|
||||
bool interfaceExists() override;
|
||||
bool addInterface(const InterfaceConfig& config) override;
|
||||
bool deleteInterface() override;
|
||||
|
||||
bool updatePeer(const InterfaceConfig& config) override;
|
||||
bool deletePeer(const QString& pubkey) override;
|
||||
peerStatus getPeerStatus(const QString& pubkey) override;
|
||||
|
||||
bool updateRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
|
||||
bool deleteRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
|
||||
|
||||
QString getDefaultCgroup() const { return m_cgroups; }
|
||||
QString getExcludeCgroup() const;
|
||||
QString getBlockCgroup() const;
|
||||
|
||||
private:
|
||||
QStringList currentInterfaces();
|
||||
bool setPeerEndpoint(struct sockaddr* sa, const QString& address, int port);
|
||||
bool addPeerPrefix(struct wg_peer* peer, const IPAddressRange& prefix);
|
||||
bool rtmSendRule(int action, int flags, int addrfamily);
|
||||
bool rtmSendRoute(int action, int flags, const IPAddressRange& prefix,
|
||||
int hopindex);
|
||||
static bool setupCgroupClass(const QString& path, unsigned long classid);
|
||||
static bool buildAllowedIp(struct wg_allowedip*,
|
||||
const IPAddressRange& prefix);
|
||||
|
||||
static QString printablePubkey(const QString& pubkey);
|
||||
|
||||
int m_nlsock = -1;
|
||||
int m_nlseq = 0;
|
||||
QSocketNotifier* m_notifier = nullptr;
|
||||
QString m_cgroups;
|
||||
|
||||
private slots:
|
||||
void nlsockReady();
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILSLINUX_H
|
Loading…
Add table
Add a link
Reference in a new issue