prepared to build

This commit is contained in:
leetthewire 2022-03-22 03:40:47 -07:00
parent 1a144da36d
commit 7d7b6f4475
44 changed files with 3863 additions and 4 deletions

View file

@ -940,8 +940,8 @@ void SshConnectionPrivate::connectToHost()
this, &SshConnectionPrivate::handleSocketConnected);
connect(m_socket, &QIODevice::readyRead,
this, &SshConnectionPrivate::handleIncomingData);
connect(m_socket, &QAbstractSocket::errorOccurred,
this, &SshConnectionPrivate::handleSocketError);
//connect(m_socket, &QAbstractSocket::errorOccurred,
// this, &SshConnectionPrivate::handleSocketError);
connect(m_socket, &QAbstractSocket::disconnected,
this, &SshConnectionPrivate::handleSocketDisconnected);
connect(&m_timeoutTimer, &QTimer::timeout, this, &SshConnectionPrivate::handleTimeout);

View file

@ -15,7 +15,7 @@ include("3rd/QtSsh/src/ssh/qssh.pri")
include("3rd/QtSsh/src/botan/botan.pri")
!android:!ios:include("3rd/SingleApplication/singleapplication.pri")
include ("3rd/SortFilterProxyModel/SortFilterProxyModel.pri")
include("3rd/QZXing/src/QZXing-components.pri")
include("3rd/qzxing/src/QZXing-components.pri")
INCLUDEPATH += $$PWD/3rd/OpenSSL/include
DEPENDPATH += $$PWD/3rd/OpenSSL/include
@ -197,6 +197,8 @@ linux:!android {
LIBS += /usr/lib/x86_64-linux-gnu/libcrypto.a
LIBS += /usr/lib/x86_64-linux-gnu/libssl.a
INCLUDEPATH += $$PWD/platforms/linux
}
win32|macx|linux:!android {

View file

@ -0,0 +1,36 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "backendlogsobserver.h"
#include "leakdetector.h"
#include "logger.h"
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
namespace {
Logger logger({LOG_LINUX, LOG_CONTROLLER}, "BackendLogsObserver");
}
BackendLogsObserver::BackendLogsObserver(
QObject* parent, std::function<void(const QString&)>&& callback)
: QObject(parent), m_callback(std::move(callback)) {
MVPN_COUNT_CTOR(BackendLogsObserver);
}
BackendLogsObserver::~BackendLogsObserver() {
MVPN_COUNT_DTOR(BackendLogsObserver);
}
void BackendLogsObserver::completed(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QString> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
m_callback("Failed to retrieve logs from the mozillavpn linuxdaemon.");
return;
}
QString status = reply.argumentAt<0>();
m_callback(status);
}

View file

@ -0,0 +1,29 @@
/* 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 BACKENDLOGSOBSERVER_H
#define BACKENDLOGSOBSERVER_H
#include <functional>
#include <QObject>
class QDBusPendingCallWatcher;
class BackendLogsObserver final : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(BackendLogsObserver)
public:
BackendLogsObserver(QObject* parent,
std::function<void(const QString&)>&& callback);
~BackendLogsObserver();
public slots:
void completed(QDBusPendingCallWatcher* call);
private:
std::function<void(const QString&)> m_callback;
};
#endif // BACKENDLOGSOBSERVER_H

View file

@ -0,0 +1,109 @@
/* 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);
}
}

View file

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef 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

View file

@ -0,0 +1,249 @@
/* 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();
}

View file

@ -0,0 +1,72 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef 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

View file

@ -0,0 +1,170 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DBUSTYPESLINUX_H
#define DBUSTYPESLINUX_H
#include <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

View file

@ -0,0 +1,206 @@
/* 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;

View file

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DNSUTILSLINUX_H
#define DNSUTILSLINUX_H
#include "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

View file

@ -0,0 +1,150 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "iputilslinux.h"
#include "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;
}

View file

@ -0,0 +1,31 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef IPUTILSLINUX_H
#define IPUTILSLINUX_H
#include "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

View file

@ -0,0 +1,58 @@
/* 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;

View file

@ -0,0 +1,28 @@
<?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>

View file

@ -0,0 +1,5 @@
[D-BUS Service]
User=root
Name=org.mozilla.vpn.dbus
Exec=/usr/bin/mozillavpn linuxdaemon
SystemdService=mozillavpn.service

View file

@ -0,0 +1,46 @@
<!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>

View file

@ -0,0 +1,25 @@
<?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>

View file

@ -0,0 +1,227 @@
/* 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;
}

View file

@ -0,0 +1,72 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef PIDTRACKER_H
#define PIDTRACKER_H
#include <QHash>
#include <QList>
#include <QSocketNotifier>
#include <QString>
#include "leakdetector.h"
struct cn_msg;
class ProcessGroup {
public:
ProcessGroup(const QString& groupName, int groupRootPid,
const QString& groupState = "active") {
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

View file

@ -0,0 +1,66 @@
/* 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);
}

View file

@ -0,0 +1,23 @@
/* 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

View file

@ -0,0 +1,648 @@
/* 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);
}
}

View file

@ -0,0 +1,56 @@
/* 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

View file

@ -0,0 +1,126 @@
/* 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 "dbusclient.h"
#include "ipaddressrange.h"
#include "leakdetector.h"
#include "logger.h"
#include "models/device.h"
#include "models/keys.h"
#include "models/server.h"
#include "mozillavpn.h"
#include "settingsholder.h"
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
constexpr const char* DBUS_SERVICE = "org.mozilla.vpn.dbus";
constexpr const char* DBUS_PATH = "/";
namespace {
Logger logger(LOG_LINUX, "DBusClient");
}
DBusClient::DBusClient(QObject* parent) : QObject(parent) {
MVPN_COUNT_CTOR(DBusClient);
m_dbus = new OrgMozillaVpnDbusInterface(DBUS_SERVICE, DBUS_PATH,
QDBusConnection::systemBus(), this);
connect(m_dbus, &OrgMozillaVpnDbusInterface::connected, this,
&DBusClient::connected);
connect(m_dbus, &OrgMozillaVpnDbusInterface::disconnected, this,
&DBusClient::disconnected);
}
DBusClient::~DBusClient() { MVPN_COUNT_DTOR(DBusClient); }
QDBusPendingCallWatcher* DBusClient::version() {
logger.debug() << "Version via DBus";
QDBusPendingReply<QString> reply = m_dbus->version();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
&QDBusPendingCallWatcher::deleteLater);
return watcher;
}
QDBusPendingCallWatcher* DBusClient::activate(
const Server& server, const Device* device, const Keys* keys, int hopindex,
const QList<IPAddressRange>& allowedIPAddressRanges,
const QStringList& vpnDisabledApps, const QHostAddress& dnsServer) {
QJsonObject json;
json.insert("privateKey", QJsonValue(keys->privateKey()));
json.insert("deviceIpv4Address", QJsonValue(device->ipv4Address()));
json.insert("deviceIpv6Address", QJsonValue(device->ipv6Address()));
json.insert("serverIpv4Gateway", QJsonValue(server.ipv4Gateway()));
json.insert("serverIpv6Gateway", QJsonValue(server.ipv6Gateway()));
json.insert("serverPublicKey", QJsonValue(server.publicKey()));
json.insert("serverIpv4AddrIn", QJsonValue(server.ipv4AddrIn()));
json.insert("serverIpv6AddrIn", QJsonValue(server.ipv6AddrIn()));
json.insert("serverPort", QJsonValue((double)server.choosePort()));
json.insert("dnsServer", QJsonValue(dnsServer.toString()));
json.insert("hopindex", QJsonValue((double)hopindex));
QJsonArray allowedIPAddesses;
for (const IPAddressRange& i : allowedIPAddressRanges) {
QJsonObject range;
range.insert("address", QJsonValue(i.ipAddress()));
range.insert("range", QJsonValue((double)i.range()));
range.insert("isIpv6", QJsonValue(i.type() == IPAddressRange::IPv6));
allowedIPAddesses.append(range);
};
json.insert("allowedIPAddressRanges", allowedIPAddesses);
QJsonArray disabledApps;
for (const QString& i : vpnDisabledApps) {
disabledApps.append(QJsonValue(i));
logger.debug() << "Disabling:" << i;
}
json.insert("vpnDisabledApps", disabledApps);
logger.debug() << "Activate via DBus";
QDBusPendingReply<bool> reply =
m_dbus->activate(QJsonDocument(json).toJson(QJsonDocument::Compact));
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
&QDBusPendingCallWatcher::deleteLater);
return watcher;
}
QDBusPendingCallWatcher* DBusClient::deactivate() {
logger.debug() << "Deactivate via DBus";
QDBusPendingReply<bool> reply = m_dbus->deactivate();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
&QDBusPendingCallWatcher::deleteLater);
return watcher;
}
QDBusPendingCallWatcher* DBusClient::status() {
logger.debug() << "Status via DBus";
QDBusPendingReply<QString> reply = m_dbus->status();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
&QDBusPendingCallWatcher::deleteLater);
return watcher;
}
QDBusPendingCallWatcher* DBusClient::getLogs() {
logger.debug() << "Get logs via DBus";
QDBusPendingReply<QString> reply = m_dbus->getLogs();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
&QDBusPendingCallWatcher::deleteLater);
return watcher;
}
QDBusPendingCallWatcher* DBusClient::cleanupLogs() {
logger.debug() << "Cleanup logs via DBus";
QDBusPendingReply<QString> reply = m_dbus->cleanupLogs();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
&QDBusPendingCallWatcher::deleteLater);
return watcher;
}

View file

@ -0,0 +1,51 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef DBUSCLIENT_H
#define DBUSCLIENT_H
#include "dbus_interface.h"
#include <QList>
#include <QObject>
#include <QHostAddress>
class Server;
class Device;
class Keys;
class IPAddressRange;
class QDBusPendingCallWatcher;
class DBusClient final : public QObject {
Q_OBJECT
Q_DISABLE_COPY_MOVE(DBusClient)
public:
DBusClient(QObject* parent);
~DBusClient();
QDBusPendingCallWatcher* version();
QDBusPendingCallWatcher* activate(
const Server& server, const Device* device, const Keys* keys,
int hopindex, const QList<IPAddressRange>& allowedIPAddressRanges,
const QStringList& vpnDisabledApps, const QHostAddress& dnsServer);
QDBusPendingCallWatcher* deactivate();
QDBusPendingCallWatcher* status();
QDBusPendingCallWatcher* getLogs();
QDBusPendingCallWatcher* cleanupLogs();
signals:
void connected(int hopindex);
void disconnected(int hopindex);
private:
OrgMozillaVpnDbusInterface* m_dbus;
};
#endif // DBUSCLIENT_H

View file

@ -0,0 +1,79 @@
/* 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 "linuxappimageprovider.h"
#include "logger.h"
#include "leakdetector.h"
#include <QDir>
#include <QDirIterator>
#include <QProcessEnvironment>
#include <QString>
#include <QSettings>
#include <QIcon>
constexpr const char* PIXMAP_FALLBACK_PATH = "/usr/share/pixmaps/";
constexpr const char* DESKTOP_ICON_LOCATION = "/usr/share/icons/";
namespace {
Logger logger(LOG_CONTROLLER, "LinuxAppImageProvider");
}
LinuxAppImageProvider::LinuxAppImageProvider(QObject* parent)
: AppImageProvider(parent, QQuickImageProvider::Image,
QQmlImageProviderBase::ForceAsynchronousImageLoading) {
MVPN_COUNT_CTOR(LinuxAppImageProvider);
QStringList searchPaths = QIcon::fallbackSearchPaths();
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
if (pe.contains("XDG_DATA_DIRS")) {
QStringList parts = pe.value("XDG_DATA_DIRS").split(":");
for (const QString& part : parts) {
addFallbackPaths(part + "/icons", searchPaths);
}
} else {
addFallbackPaths(DESKTOP_ICON_LOCATION, searchPaths);
}
if (pe.contains("HOME")) {
addFallbackPaths(pe.value("HOME") + "/.local/share/icons", searchPaths);
}
searchPaths << PIXMAP_FALLBACK_PATH;
QIcon::setFallbackSearchPaths(searchPaths);
}
LinuxAppImageProvider::~LinuxAppImageProvider() {
MVPN_COUNT_DTOR(LinuxAppImageProvider);
}
void LinuxAppImageProvider::addFallbackPaths(const QString& iconDir,
QStringList& searchPaths) {
searchPaths << iconDir;
QDirIterator iter(iconDir, QDir::Dirs | QDir::NoDotAndDotDot);
while (iter.hasNext()) {
QFileInfo fileinfo(iter.next());
logger.debug() << "Adding QIcon fallback:" << fileinfo.absoluteFilePath();
searchPaths << fileinfo.absoluteFilePath();
}
}
// from QQuickImageProvider
QImage LinuxAppImageProvider::requestImage(const QString& id, QSize* size,
const QSize& requestedSize) {
QSettings entry(id, QSettings::IniFormat);
entry.beginGroup("Desktop Entry");
QString name = entry.value("Icon").toString();
QIcon icon = QIcon::fromTheme(name);
QPixmap pixmap = icon.pixmap(requestedSize);
size->setHeight(pixmap.height());
size->setWidth(pixmap.width());
logger.debug() << "Loaded icon" << icon.name() << "size:" << pixmap.width()
<< "x" << pixmap.height();
return pixmap.toImage();
}

View file

@ -0,0 +1,22 @@
/* 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 LINUXAPPIMAGEPROVIDER_H
#define LINUXAPPIMAGEPROVIDER_H
#include "appimageprovider.h"
class LinuxAppImageProvider final : public AppImageProvider {
public:
LinuxAppImageProvider(QObject* parent);
~LinuxAppImageProvider();
QImage requestImage(const QString& id, QSize* size,
const QSize& requestedSize) override;
private:
static void addFallbackPaths(const QString& dataDir,
QStringList& fallbackPaths);
};
#endif // LINUXAPPIMAGEPROVIDER_H

View file

@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "linuxapplistprovider.h"
#include "leakdetector.h"
#include <QProcess>
#include <QString>
#include <QDir>
#include <QDirIterator>
#include <QSettings>
#include <QProcessEnvironment>
#include "logger.h"
#include "leakdetector.h"
constexpr const char* DESKTOP_ENTRY_LOCATION = "/usr/share/applications/";
namespace {
Logger logger(LOG_CONTROLLER, "LinuxAppListProvider");
}
LinuxAppListProvider::LinuxAppListProvider(QObject* parent)
: AppListProvider(parent) {
MVPN_COUNT_CTOR(LinuxAppListProvider);
}
LinuxAppListProvider::~LinuxAppListProvider() {
MVPN_COUNT_DTOR(LinuxAppListProvider);
}
void LinuxAppListProvider::fetchEntries(const QString& dataDir,
QMap<QString, QString>& map) {
logger.debug() << "Fetch Application list from" << dataDir;
QDirIterator iter(dataDir, QStringList() << "*.desktop", QDir::Files);
while (iter.hasNext()) {
QFileInfo fileinfo(iter.next());
QSettings entry(fileinfo.filePath(), QSettings::IniFormat);
entry.beginGroup("Desktop Entry");
/* Filter out everything except visible applications. */
if (entry.value("Type").toString() != "Application") {
continue;
}
if (entry.value("NoDisplay", QVariant(false)).toBool()) {
continue;
}
map[fileinfo.absoluteFilePath()] = entry.value("Name").toString();
}
}
void LinuxAppListProvider::getApplicationList() {
logger.debug() << "Fetch Application list from Linux desktop";
QMap<QString, QString> out;
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
if (pe.contains("XDG_DATA_DIRS")) {
QStringList parts = pe.value("XDG_DATA_DIRS").split(":");
for (const QString& part : parts) {
fetchEntries(part.trimmed() + "/applications", out);
}
} else {
fetchEntries(DESKTOP_ENTRY_LOCATION, out);
}
if (pe.contains("HOME")) {
fetchEntries(pe.value("HOME") + "/.local/share/applications", out);
}
emit newAppList(out);
}

View file

@ -0,0 +1,23 @@
/* 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 LINUXAPPLISTPROVIDER_H
#define LINUXAPPLISTPROVIDER_H
#include <applistprovider.h>
#include <QObject>
#include <QProcess>
class LinuxAppListProvider final : public AppListProvider {
Q_OBJECT
public:
explicit LinuxAppListProvider(QObject* parent);
~LinuxAppListProvider();
void getApplicationList() override;
private:
void fetchEntries(const QString& dataDir, QMap<QString, QString>& map);
};
#endif // LINUXAPPLISTPROVIDER_H

View file

@ -0,0 +1,213 @@
/* 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 "linuxcontroller.h"
#include "backendlogsobserver.h"
#include "dbusclient.h"
#include "errorhandler.h"
#include "ipaddressrange.h"
#include "leakdetector.h"
#include "logger.h"
#include "models/device.h"
#include "models/keys.h"
#include "models/server.h"
#include "mozillavpn.h"
#include <QDBusPendingCallWatcher>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QProcess>
#include <QString>
namespace {
Logger logger({LOG_LINUX, LOG_CONTROLLER}, "LinuxController");
}
LinuxController::LinuxController() {
MVPN_COUNT_CTOR(LinuxController);
m_dbus = new DBusClient(this);
connect(m_dbus, &DBusClient::connected, this, &LinuxController::hopConnected);
connect(m_dbus, &DBusClient::disconnected, this,
&LinuxController::hopDisconnected);
}
LinuxController::~LinuxController() { MVPN_COUNT_DTOR(LinuxController); }
void LinuxController::initialize(const Device* device, const Keys* keys) {
Q_UNUSED(device);
Q_UNUSED(keys);
QDBusPendingCallWatcher* watcher = m_dbus->status();
connect(watcher, &QDBusPendingCallWatcher::finished, this,
&LinuxController::initializeCompleted);
}
void LinuxController::initializeCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QString> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
emit initialized(false, false, QDateTime());
return;
}
QString status = reply.argumentAt<0>();
logger.debug() << "Status:" << status;
QJsonDocument json = QJsonDocument::fromJson(status.toLocal8Bit());
Q_ASSERT(json.isObject());
QJsonObject obj = json.object();
Q_ASSERT(obj.contains("status"));
QJsonValue statusValue = obj.value("status");
Q_ASSERT(statusValue.isBool());
emit initialized(true, statusValue.toBool(), QDateTime::currentDateTime());
}
void LinuxController::activate(
const QList<Server>& serverList, const Device* device, const Keys* keys,
const QList<IPAddressRange>& allowedIPAddressRanges,
const QList<QString>& vpnDisabledApps, const QHostAddress& dnsServer,
Reason reason) {
Q_UNUSED(reason);
Q_UNUSED(vpnDisabledApps);
// Activate connections starting from the outermost tunnel
for (int hopindex = serverList.count() - 1; hopindex > 0; hopindex--) {
const Server& hop = serverList[hopindex];
const Server& next = serverList[hopindex - 1];
QList<IPAddressRange> hopAddressRanges = {
IPAddressRange(next.ipv4AddrIn()), IPAddressRange(next.ipv6AddrIn())};
logger.debug() << "LinuxController hopindex" << hopindex << "activated";
connect(m_dbus->activate(hop, device, keys, hopindex, hopAddressRanges,
QStringList(), QHostAddress(hop.ipv4Gateway())),
&QDBusPendingCallWatcher::finished, this,
&LinuxController::operationCompleted);
}
// Activate the final hop last
logger.debug() << "LinuxController activated";
const Server& server = serverList[0];
connect(m_dbus->activate(server, device, keys, 0, allowedIPAddressRanges,
vpnDisabledApps, dnsServer),
&QDBusPendingCallWatcher::finished, this,
&LinuxController::operationCompleted);
}
void LinuxController::deactivate(Reason reason) {
logger.debug() << "LinuxController deactivated";
if (reason == ReasonSwitching) {
logger.debug() << "No disconnect for quick server switching";
emit disconnected();
return;
}
connect(m_dbus->deactivate(), &QDBusPendingCallWatcher::finished, this,
&LinuxController::operationCompleted);
}
void LinuxController::operationCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<bool> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError);
emit disconnected();
return;
}
bool status = reply.argumentAt<0>();
if (status) {
logger.debug() << "DBus service says: all good.";
// we will receive the connected/disconnected() signal;
return;
}
logger.error() << "DBus service says: error.";
MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError);
emit disconnected();
}
void LinuxController::hopConnected(int hopindex) {
if (hopindex == 0) {
logger.debug() << "LinuxController connected";
emit connected();
} else {
logger.debug() << "LinuxController hopindex" << hopindex << "connected";
}
}
void LinuxController::hopDisconnected(int hopindex) {
if (hopindex == 0) {
logger.debug() << "LinuxController disconnected";
emit disconnected();
} else {
logger.debug() << "LinuxController hopindex" << hopindex << "disconnected";
}
}
void LinuxController::checkStatus() {
logger.debug() << "Check status";
QDBusPendingCallWatcher* watcher = m_dbus->status();
connect(watcher, &QDBusPendingCallWatcher::finished, this,
&LinuxController::checkStatusCompleted);
}
void LinuxController::checkStatusCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QString> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
return;
}
QString status = reply.argumentAt<0>();
logger.debug() << "Status:" << status;
QJsonDocument json = QJsonDocument::fromJson(status.toLocal8Bit());
Q_ASSERT(json.isObject());
QJsonObject obj = json.object();
Q_ASSERT(obj.contains("status"));
QJsonValue statusValue = obj.value("status");
Q_ASSERT(statusValue.isBool());
if (!statusValue.toBool()) {
logger.error() << "Unable to retrieve the status from the interface.";
return;
}
Q_ASSERT(obj.contains("serverIpv4Gateway"));
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
Q_ASSERT(serverIpv4Gateway.isString());
Q_ASSERT(obj.contains("deviceIpv4Address"));
QJsonValue deviceIpv4Address = obj.value("deviceIpv4Address");
Q_ASSERT(deviceIpv4Address.isString());
Q_ASSERT(obj.contains("txBytes"));
QJsonValue txBytes = obj.value("txBytes");
Q_ASSERT(txBytes.isDouble());
Q_ASSERT(obj.contains("rxBytes"));
QJsonValue rxBytes = obj.value("rxBytes");
Q_ASSERT(rxBytes.isDouble());
emit statusUpdated(serverIpv4Gateway.toString(), deviceIpv4Address.toString(),
txBytes.toDouble(), rxBytes.toDouble());
}
void LinuxController::getBackendLogs(
std::function<void(const QString&)>&& a_callback) {
std::function<void(const QString&)> callback = std::move(a_callback);
QDBusPendingCallWatcher* watcher = m_dbus->getLogs();
connect(watcher, &QDBusPendingCallWatcher::finished,
new BackendLogsObserver(this, std::move(callback)),
&BackendLogsObserver::completed);
}
void LinuxController::cleanupBackendLogs() { m_dbus->cleanupLogs(); }

View file

@ -0,0 +1,49 @@
/* 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 LINUXCONTROLLER_H
#define LINUXCONTROLLER_H
#include "controllerimpl.h"
#include <QObject>
class DBusClient;
class QDBusPendingCallWatcher;
class LinuxController final : public ControllerImpl {
Q_DISABLE_COPY_MOVE(LinuxController)
public:
LinuxController();
~LinuxController();
void initialize(const Device* device, const Keys* keys) override;
void activate(const QList<Server>& serverList, const Device* device,
const Keys* keys,
const QList<IPAddressRange>& allowedIPAddressRanges,
const QList<QString>& vpnDisabledApps,
const QHostAddress& dnsServer, Reason reason) override;
void deactivate(Reason reason) override;
void checkStatus() override;
void getBackendLogs(std::function<void(const QString&)>&& callback) override;
void cleanupBackendLogs() override;
private slots:
void checkStatusCompleted(QDBusPendingCallWatcher* call);
void initializeCompleted(QDBusPendingCallWatcher* call);
void operationCompleted(QDBusPendingCallWatcher* call);
void hopConnected(int hopindex);
void hopDisconnected(int hopindex);
private:
DBusClient* m_dbus = nullptr;
};
#endif // LINUXCONTROLLER_H

View file

@ -0,0 +1,17 @@
/* 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 "cryptosettings.h"
void CryptoSettings::resetKey() {}
bool CryptoSettings::getKey(uint8_t key[CRYPTO_SETTINGS_KEY_SIZE]) {
Q_UNUSED(key);
return false;
}
// static
CryptoSettings::Version CryptoSettings::getSupportedVersion() {
return CryptoSettings::NoEncryption;
}

View file

@ -0,0 +1,127 @@
/* 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 "dbusclient.h"
#include "logger.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <mntent.h>
constexpr const char* WG_QUICK = "wg-quick";
namespace {
Logger logger(LOG_LINUX, "LinuxDependencies");
void showAlert(const QString& message) {
logger.debug() << "Show alert:" << message;
QMessageBox alert;
alert.setText(message);
alert.exec();
}
bool findInPath(const char* what) {
char* path = getenv("PATH");
Q_ASSERT(path);
QStringList parts = QString(path).split(":");
for (const QString& part : parts) {
QDir pathDir(part);
QFileInfo file(pathDir.filePath(what));
if (file.exists()) {
logger.debug() << what << "found" << file.filePath();
return true;
}
}
return false;
}
bool checkDaemonVersion() {
logger.debug() << "Check Daemon Version";
DBusClient* dbus = new DBusClient(nullptr);
QDBusPendingCallWatcher* watcher = dbus->version();
bool completed = false;
bool value = false;
QObject::connect(
watcher, &QDBusPendingCallWatcher::finished,
[completed = &completed, value = &value](QDBusPendingCallWatcher* call) {
*completed = true;
QDBusPendingReply<QString> reply = *call;
if (reply.isError()) {
logger.error() << "DBus message received - error";
*value = false;
return;
}
QString version = reply.argumentAt<0>();
*value = version == PROTOCOL_VERSION;
logger.debug() << "DBus message received - daemon version:" << version
<< " - current version:" << PROTOCOL_VERSION;
});
while (!completed) {
QCoreApplication::processEvents();
}
delete dbus;
return value;
}
} // namespace
// static
bool LinuxDependencies::checkDependencies() {
char* path = getenv("PATH");
if (!path) {
showAlert("No PATH env found.");
return false;
}
if (!findInPath(WG_QUICK)) {
showAlert("Unable to locate wg-quick");
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();
}

View file

@ -0,0 +1,22 @@
/* 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);
private:
LinuxDependencies() = default;
~LinuxDependencies() = default;
Q_DISABLE_COPY(LinuxDependencies)
};
#endif // LINUXDEPENDENCIES_H

View file

@ -0,0 +1,55 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "linuxnetworkwatcher.h"
#include "linuxnetworkwatcherworker.h"
#include "leakdetector.h"
#include "logger.h"
#include "timersingleshot.h"
namespace {
Logger logger(LOG_LINUX, "LinuxNetworkWatcher");
}
LinuxNetworkWatcher::LinuxNetworkWatcher(QObject* parent)
: NetworkWatcherImpl(parent) {
MVPN_COUNT_CTOR(LinuxNetworkWatcher);
m_thread.start();
}
LinuxNetworkWatcher::~LinuxNetworkWatcher() {
MVPN_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.
TimerSingleShot::create(this, 2000, [this]() {
QMetaObject::invokeMethod(m_worker, "initialize", Qt::QueuedConnection);
});
}
void LinuxNetworkWatcher::start() {
logger.debug() << "actived";
NetworkWatcherImpl::start();
emit checkDevicesInThread();
}

View file

@ -0,0 +1,33 @@
/* 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 "networkwatcherimpl.h"
#include <QThread>
class LinuxNetworkWatcherWorker;
class LinuxNetworkWatcher final : public NetworkWatcherImpl {
Q_OBJECT
public:
explicit LinuxNetworkWatcher(QObject* parent);
~LinuxNetworkWatcher();
void initialize() override;
void start() override;
signals:
void checkDevicesInThread();
private:
LinuxNetworkWatcherWorker* m_worker = nullptr;
QThread m_thread;
};
#endif // LINUXNETWORKWATCHER_H

View file

@ -0,0 +1,175 @@
/* 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 "leakdetector.h"
#include "logger.h"
#include <QtDBus/QtDBus>
// 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(LOG_LINUX, "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) {
MVPN_COUNT_CTOR(LinuxNetworkWatcherWorker);
moveToThread(thread);
}
LinuxNetworkWatcherWorker::~LinuxNetworkWatcherWorker() {
MVPN_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:" << ssid;
emit unsecuredNetwork(ssid, bssid);
break;
}
}
}

View file

@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef 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

View file

@ -0,0 +1,191 @@
/* 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 "linuxpingsender.h"
#include "leakdetector.h"
#include "logger.h"
#include <QSocketNotifier>
#include <arpa/inet.h>
#include <errno.h>
#include <linux/filter.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>
#include <unistd.h>
namespace {
Logger logger({LOG_LINUX, LOG_NETWORKING}, "LinuxPingSender");
}
int LinuxPingSender::createSocket() {
// Try creating an ICMP socket. This would be the ideal choice, but it can
// fail depending on the kernel config (see: sys.net.ipv4.ping_group_range)
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (m_socket >= 0) {
m_ident = 0;
return m_socket;
}
if ((errno != EPERM) && (errno != EACCES)) {
return -1;
}
// As a fallback, create a raw socket, which requires root permissions
// or CAP_NET_RAW to be granted to the VPN client.
m_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (m_socket < 0) {
return -1;
}
m_ident = getpid() & 0xffff;
// Attach a BPF filter to discard everything but replies to our echo.
struct sock_filter bpf_prog[] = {
BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* Skip IP header. */
BPF_STMT(BPF_LD | BPF_H | BPF_IND, 4), /* Load icmp echo ident */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, m_ident, 1, 0), /* Ours? */
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected identifier. Reject. */
BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), /* Load icmp type */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected type. Reject. */
BPF_STMT(BPF_RET | BPF_K, ~0U), /* Packet passes the filter. */
};
struct sock_fprog filter = {
.len = sizeof(bpf_prog) / sizeof(struct sock_filter),
.filter = bpf_prog,
};
setsockopt(m_socket, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
return m_socket;
}
LinuxPingSender::LinuxPingSender(const QString& source, QObject* parent)
: PingSender(parent), m_source(source) {
MVPN_COUNT_CTOR(LinuxPingSender);
logger.debug() << "LinuxPingSender(" + source + ") created";
m_socket = createSocket();
if (m_socket < 0) {
logger.error() << "Socket creation error: " << strerror(errno);
return;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
if (inet_aton(source.toLocal8Bit().constData(), &addr.sin_addr) == 0) {
logger.error() << "source" << source << "error:" << strerror(errno);
return;
}
if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
close(m_socket);
m_socket = -1;
logger.error() << "bind error:" << strerror(errno);
return;
}
m_notifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, this);
if (m_ident) {
connect(m_notifier, &QSocketNotifier::activated, this,
&LinuxPingSender::rawSocketReady);
} else {
connect(m_notifier, &QSocketNotifier::activated, this,
&LinuxPingSender::icmpSocketReady);
}
}
LinuxPingSender::~LinuxPingSender() {
MVPN_COUNT_DTOR(LinuxPingSender);
if (m_socket >= 0) {
close(m_socket);
}
}
void LinuxPingSender::sendPing(const QString& dest, quint16 sequence) {
// QProcess is not supported on iOS. Because of this we cannot use the `ping`
// app as fallback on this platform.
#ifndef MVPN_IOS
// Use the generic ping sender if we failed to open an ICMP socket.
if (m_socket < 0) {
QStringList args;
args << "-c"
<< "1";
args << "-I" << m_source;
args << dest;
genericSendPing(args, sequence);
return;
}
#endif
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
if (inet_aton(dest.toLocal8Bit().constData(), &addr.sin_addr) == 0) {
return;
}
struct icmphdr packet;
memset(&packet, 0, sizeof(packet));
packet.type = ICMP_ECHO;
packet.un.echo.id = htons(m_ident);
packet.un.echo.sequence = htons(sequence);
packet.checksum = inetChecksum(&packet, sizeof(packet));
int rc = sendto(m_socket, &packet, sizeof(packet), 0, (struct sockaddr*)&addr,
sizeof(addr));
if (rc < 0) {
logger.error() << "failed to send:" << strerror(errno);
}
}
void LinuxPingSender::icmpSocketReady() {
socklen_t slen = 0;
unsigned char data[2048];
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
if (rc <= 0) {
logger.error() << "recvfrom failed:" << strerror(errno);
return;
}
struct icmphdr packet;
if (rc >= (int)sizeof(packet)) {
memcpy(&packet, data, sizeof(packet));
if (packet.type == ICMP_ECHOREPLY) {
emit recvPing(htons(packet.un.echo.sequence));
}
}
}
void LinuxPingSender::rawSocketReady() {
socklen_t slen = 0;
unsigned char data[2048];
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
if (rc <= 0) {
logger.error() << "recvfrom failed:" << strerror(errno);
return;
}
// Check the IP header
const struct iphdr* ip = (struct iphdr*)data;
int iphdrlen = ip->ihl * 4;
if (rc < iphdrlen || iphdrlen < (int)sizeof(struct iphdr)) {
logger.error() << "malformed IP packet:" << strerror(errno);
return;
}
// Check the ICMP packet
struct icmphdr packet;
if (inetChecksum(data + iphdrlen, rc - iphdrlen) != 0) {
logger.warning() << "invalid checksum";
return;
}
if (rc >= (iphdrlen + (int)sizeof(packet))) {
memcpy(&packet, data + iphdrlen, sizeof(packet));
quint16 id = htons(m_ident);
if ((packet.type == ICMP_ECHOREPLY) && (packet.un.echo.id == id)) {
emit recvPing(htons(packet.un.echo.sequence));
}
}
}

View 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 LINUXPINGSENDER_H
#define LINUXPINGSENDER_H
#include "pingsender.h"
#include <QObject>
class QSocketNotifier;
class LinuxPingSender final : public PingSender {
Q_OBJECT
Q_DISABLE_COPY_MOVE(LinuxPingSender)
public:
LinuxPingSender(const QString& source, QObject* parent = nullptr);
~LinuxPingSender();
void sendPing(const QString& dest, quint16 sequence) override;
private:
int createSocket();
private slots:
void rawSocketReady();
void icmpSocketReady();
private:
QSocketNotifier* m_notifier = nullptr;
QString m_source;
int m_socket = 0;
quint16 m_ident = 0;
};
#endif // LINUXPINGSENDER_H

View file

@ -0,0 +1,109 @@
/* 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 "platforms/linux/linuxsystemtraynotificationhandler.h"
#include "constants.h"
#include "leakdetector.h"
#include "logger.h"
#include <QtDBus/QtDBus>
constexpr const char* DBUS_ITEM = "org.freedesktop.Notifications";
constexpr const char* DBUS_PATH = "/org/freedesktop/Notifications";
constexpr const char* DBUS_INTERFACE = "org.freedesktop.Notifications";
constexpr const char* ACTION_ID = "mozilla_vpn_notification";
namespace {
Logger logger(LOG_LINUX, "LinuxSystemTrayNotificationHandler");
} // namespace
// static
bool LinuxSystemTrayNotificationHandler::requiredCustomImpl() {
if (!QDBusConnection::sessionBus().isConnected()) {
return false;
}
QDBusConnectionInterface* interface =
QDBusConnection::sessionBus().interface();
if (!interface) {
return false;
}
// This custom systemTrayHandler implementation is required only on Unity.
QStringList registeredServices = interface->registeredServiceNames().value();
return registeredServices.contains("com.canonical.Unity");
}
LinuxSystemTrayNotificationHandler::LinuxSystemTrayNotificationHandler(
QObject* parent)
: SystemTrayNotificationHandler(parent) {
MVPN_COUNT_CTOR(LinuxSystemTrayNotificationHandler);
QDBusConnection::sessionBus().connect(DBUS_ITEM, DBUS_PATH, DBUS_INTERFACE,
"ActionInvoked", this,
SLOT(actionInvoked(uint, QString)));
}
LinuxSystemTrayNotificationHandler::~LinuxSystemTrayNotificationHandler() {
MVPN_COUNT_DTOR(LinuxSystemTrayNotificationHandler);
}
void LinuxSystemTrayNotificationHandler::notify(Message type,
const QString& title,
const QString& message,
int timerMsec) {
QString actionMessage;
switch (type) {
case None:
return SystemTrayNotificationHandler::notify(type, title, message,
timerMsec);
case UnsecuredNetwork:
actionMessage = qtTrId("vpn.toggle.on");
break;
case CaptivePortalBlock:
actionMessage = qtTrId("vpn.toggle.off");
break;
case CaptivePortalUnblock:
actionMessage = qtTrId("vpn.toggle.on");
break;
default:
Q_ASSERT(false);
}
m_lastMessage = type;
emit notificationShown(title, message);
QDBusInterface n(DBUS_ITEM, DBUS_PATH, DBUS_INTERFACE,
QDBusConnection::sessionBus());
if (!n.isValid()) {
qWarning("Failed to connect to the notification manager via system dbus");
return;
}
uint32_t replacesId = 0; // Don't replace.
const char* appIcon = MVPN_ICON_PATH;
QStringList actions{ACTION_ID, actionMessage};
QMap<QString, QVariant> hints;
QDBusReply<uint> reply = n.call("Notify", "Mozilla VPN", replacesId, appIcon,
title, message, actions, hints, timerMsec);
if (!reply.isValid()) {
logger.warning() << "Failed to show the notification";
}
m_lastNotificationId = reply;
}
void LinuxSystemTrayNotificationHandler::actionInvoked(uint actionId,
QString action) {
logger.debug() << "Notification clicked" << actionId << action;
if (action == ACTION_ID && m_lastNotificationId == actionId) {
messageClickHandle();
}
}

View file

@ -0,0 +1,34 @@
/* 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 LINUXNOTIFICATIONNOTIFICATIONHANDLER_H
#define LINUXNOTIFICATIONNOTIFICATIONHANDLER_H
#include "./ui/systemtray_notificationhandler.h"
#include <QObject>
class LinuxSystemTrayNotificationHandler final
: public SystemTrayNotificationHandler {
Q_OBJECT
Q_DISABLE_COPY_MOVE(LinuxSystemTrayNotificationHandler)
public:
static bool requiredCustomImpl();
LinuxSystemTrayNotificationHandler(QObject* parent);
~LinuxSystemTrayNotificationHandler();
private:
void notify(Message type, const QString& title, const QString& message,
int timerMsec) override;
private slots:
void actionInvoked(uint actionId, QString action);
private:
uint m_lastNotificationId = 0;
};
#endif // LINUXNOTIFICATIONNOTIFICATIONHANDLER_H

View file

@ -12,7 +12,7 @@
#else
# if defined(Q_OS_LINUX)
# include "platforms/linux/linuxsystemtraynotificationhandler.h"
# include "linuxsystemtraynotificationhandler.h"
# endif
# include "systemtray_notificationhandler.h"