summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKari Oikarinen <kari.oikarinen@qt.io>2016-08-23 10:20:42 +0300
committerKari Oikarinen <kari.oikarinen@qt.io>2016-08-26 10:29:44 +0000
commitfb5c3c7e2cc7f8be83facbacc41509f034be5ba0 (patch)
tree7ce780fef6f45df4153c2cad08f9cc7b13a01a78
parent178198621734906e6a5c40274058d6435277fb4f (diff)
Configure USB NIC with NetworkManager
A new qdb subcommand "network" takes a MAC address and configures the network device having that MAC to use link-local IPv4 addresses. If the device is already using link-local IPv4 addresses, nothing is done. If not, the existing connections for that device are checked to see if one of them uses link-local addresses. If a suitable connection is found, that connection is activated. Otherwise a new connection is created and activated. Task-number: QTBUG-55432 Change-Id: I689eb016bffb45c0cb7999b109febd5b34b1e0e4 Reviewed-by: Kimmo Ollila <kimmo.ollila@theqtcompany.com>
-rw-r--r--client/client.pro3
-rw-r--r--client/main.cpp31
-rw-r--r--client/networkmanagercontrol.cpp437
-rw-r--r--client/networkmanagercontrol.h47
4 files changed, 516 insertions, 2 deletions
diff --git a/client/client.pro b/client/client.pro
index 6f1392c..9e127cd 100644
--- a/client/client.pro
+++ b/client/client.pro
@@ -1,4 +1,5 @@
QT -= gui
+QT += dbus
CONFIG += c++11
@@ -13,6 +14,7 @@ HEADERS += \
echoservice.h \
filepullservice.h \
filepushservice.h \
+ networkmanagercontrol.h \
processservice.h \
service.h
@@ -22,6 +24,7 @@ SOURCES += \
filepullservice.cpp \
filepushservice.cpp \
main.cpp \
+ networkmanagercontrol.cpp \
processservice.cpp \
service.cpp
diff --git a/client/main.cpp b/client/main.cpp
index 8fd31e3..3d555ec 100644
--- a/client/main.cpp
+++ b/client/main.cpp
@@ -24,6 +24,7 @@
#include "filepullservice.h"
#include "filepushservice.h"
#include "interruptsignalhandler.h"
+#include "networkmanagercontrol.h"
#include "processservice.h"
#include <QtCore/qcommandlineparser.h>
@@ -31,6 +32,7 @@
#include <QtCore/qdebug.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qtimer.h>
+#include <QtDBus/QDBusObjectPath>
void setupFilePullService(Connection *connection, const QString &sourcePath, const QString &sinkPath)
{
@@ -104,6 +106,28 @@ void setupProcessService(Connection *connection, const QString &processName, con
service->initialize();
}
+void configureUsbNetwork(const QString &macAddress)
+{
+ qDebug() << "Configuring network for" << macAddress;
+ NetworkManagerControl networkManager;
+ auto deviceResult = networkManager.findNetworkDeviceByMac(macAddress);
+ if (!deviceResult.isValid()) {
+ qWarning() << "Could not find network device" << macAddress;
+ return;
+ } else {
+ const auto networkCard = deviceResult.toString();
+ if (networkManager.isActivated(networkCard)) {
+ qDebug() << networkCard << "is activated";
+ if (networkManager.isDeviceUsingLinkLocal(networkCard)) {
+ qInfo() << networkCard << "is already using a link-local IP";
+ return;
+ }
+ }
+ if (!networkManager.activateOrCreateConnection(QDBusObjectPath{networkCard}, macAddress))
+ qWarning() << "Could not setup network settings for the USB Ethernet interface";
+ }
+}
+
int main(int argc, char *argv[])
{
QCoreApplication app{argc, argv};
@@ -137,9 +161,8 @@ int main(int argc, char *argv[])
QCoreApplication::exit(130);
});
- qDebug() << "initialized connection";
-
connection.connect();
+ qDebug() << "initialized connection";
QStringList arguments = parser.positionalArguments();
if (arguments.size() < 2)
@@ -155,6 +178,10 @@ int main(int argc, char *argv[])
} else if (command == "pull") {
Q_ASSERT(arguments.size() == 3);
setupFilePullService(&connection, arguments[1], arguments[2]);
+ } else if (command == "network") {
+ Q_ASSERT(arguments.size() == 2);
+ configureUsbNetwork(arguments[1]);
+ QTimer::singleShot(1, []() { QCoreApplication::quit(); });
} else {
qDebug() << "Unrecognized command:" << command;
return 1;
diff --git a/client/networkmanagercontrol.cpp b/client/networkmanagercontrol.cpp
new file mode 100644
index 0000000..ed32e0c
--- /dev/null
+++ b/client/networkmanagercontrol.cpp
@@ -0,0 +1,437 @@
+/******************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Debug Bridge.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+#include "networkmanagercontrol.h"
+
+#include <QtDBus>
+
+#include <algorithm>
+
+using SettingsMap = QMap<QString, QMap<QString, QDBusVariant>>;
+
+const QString networkManagerServiceName = "org.freedesktop.NetworkManager";
+const QString networkManagerObjectPath = "/org/freedesktop/NetworkManager";
+const QString networkManagerInterfaceName = "org.freedesktop.NetworkManager";
+
+QVariant connectionSettings(QDBusConnection &bus, const QDBusObjectPath &connectionPath);
+SettingsMap demarshallSettings(const QDBusArgument &settingsArgument);
+
+QVariant connectionMac(QDBusConnection &bus, const QDBusObjectPath &connectionPath)
+{
+ const auto result = connectionSettings(bus, connectionPath);
+
+ if (!result.isValid()) {
+ qWarning() << "Could not get MAC address for" << connectionPath.path();
+ return QVariant{};
+ }
+ const auto settings = result.value<SettingsMap>();
+
+ return settings["802-3-ethernet"]["mac-address"].variant();
+}
+
+QVariant connectionSettings(QDBusConnection &bus, const QDBusObjectPath &connectionPath)
+{
+ if (!bus.isConnected()) {
+ qWarning() << "Could not connect to D-Bus system bus: " << bus.lastError();
+ return QVariant{};
+ }
+ QDBusInterface connectionInterface{networkManagerServiceName,
+ connectionPath.path(),
+ networkManagerInterfaceName + ".Settings.Connection",
+ bus};
+ if (!connectionInterface.isValid()) {
+ qWarning() << "Could not find NetworkManager Connection:" << connectionPath.path() << connectionInterface.lastError();
+ return QVariant{};
+ }
+
+ QDBusMessage result = connectionInterface.call("GetSettings");
+ if (result.type() != QDBusMessage::ReplyMessage) {
+ qWarning() << "Could not get connection settings for" << connectionPath.path() << ":" << result.errorMessage();
+ return QVariant{};
+ }
+ Q_ASSERT(result.arguments().size() == 1);
+ auto settingsArgument = result.arguments()[0].value<QDBusArgument>();
+ return QVariant::fromValue(demarshallSettings(settingsArgument));
+}
+
+QVariant connectionSettingsFromDevice(QDBusConnection &bus, const QString &devicePath)
+{
+ if (!bus.isConnected()) {
+ qWarning() << "Could not connect to D-Bus system bus: " << bus.lastError();
+ return QVariant{};
+ }
+ QDBusInterface deviceInterface{networkManagerServiceName,
+ devicePath,
+ networkManagerInterfaceName + ".Device",
+ bus};
+ if (!deviceInterface.isValid()) {
+ qWarning() << "Could not find NetworkManager Device:" << devicePath << deviceInterface.lastError();
+ return QVariant{};
+ }
+
+ QDBusMessage result = deviceInterface.call("GetAppliedConnection", 0u);
+ if (result.type() != QDBusMessage::ReplyMessage) {
+ qWarning() << "Could not get connection settings for" << devicePath << ":" << result.errorMessage();
+ return QVariant{};
+ }
+ Q_ASSERT(result.arguments().size() == 2);
+ const auto settingsArgument = result.arguments()[0].value<QDBusArgument>();
+ return QVariant::fromValue(demarshallSettings(settingsArgument));
+}
+
+SettingsMap demarshallSettings(const QDBusArgument &settingsArgument)
+{
+ SettingsMap settings;
+ settingsArgument >> settings;
+ return settings;
+}
+
+QByteArray macAddressToByteArray(const QString &macAddress)
+{
+ QByteArray addressBytes;
+ const auto hexs = macAddress.split(":");
+ for (auto hex : hexs) {
+ bool ok = false;
+ addressBytes.append(static_cast<char>(hex.toInt(&ok, 16)));
+ Q_ASSERT_X(ok, "macAddressToByteArray", "Invalid MAC address given");
+ }
+ return addressBytes;
+}
+
+bool settingsUseLinkLocal(const SettingsMap &settings)
+{
+ const auto method = settings["ipv4"]["method"].variant().toString();
+ return method == "link-local";
+}
+
+NetworkManagerControl::NetworkManagerControl()
+ : m_bus{QDBusConnection::systemBus()}
+{
+ qDBusRegisterMetaType<QMap<QString, QDBusVariant>>();
+ qDBusRegisterMetaType<SettingsMap>();
+}
+
+bool NetworkManagerControl::activateOrCreateConnection(const QDBusObjectPath &devicePath, const QString &macAddress)
+{
+ qDebug() << "Activating or creating a connection";
+ const auto connectionsResult = findConnectionsByMac(macAddress);
+ if (!connectionsResult.isValid()) {
+ qWarning() << "Could not list NetworkManager connections";
+ return false;
+ }
+ const auto connections = connectionsResult.value<QList<QDBusObjectPath>>();
+
+ const auto it = std::find_if(connections.begin(), connections.end(), [this](const QDBusObjectPath path) {
+ return this->isConnectionUsingLinkLocal(path.path());
+ });
+
+ QDBusObjectPath connectionPath;
+ if (it != connections.end()) {
+ qDebug() << "Found existing connection:" << it->path();
+ connectionPath = *it;
+ } else {
+ qDebug() << "Creating new connection";
+ const auto result = createConnection(macAddress);
+ if (!result.isValid()) {
+ qWarning() << "Could not create a NetworkManager connection for" << macAddress;
+ return false;
+ }
+ connectionPath = result.value<QDBusObjectPath>();
+ qDebug() << "New connection:" << connectionPath.path();
+ }
+
+ QDBusInterface networkManagerInterface{networkManagerServiceName,
+ networkManagerObjectPath,
+ networkManagerInterfaceName,
+ m_bus};
+
+ if (!networkManagerInterface.isValid()) {
+ qWarning() << "Could not find NetworkManager D-Bus interface:"
+ << networkManagerInterface.lastError();
+ return false;
+ }
+
+ const auto response = networkManagerInterface.call("ActivateConnection",
+ QVariant::fromValue(connectionPath),
+ QVariant::fromValue(devicePath),
+ QVariant::fromValue(QDBusObjectPath{"/"}));
+
+ if (response.type() != QDBusMessage::ReplyMessage)
+ return false;
+ qDebug() << "Successfully activated" << connectionPath.path();
+ return true;
+}
+
+QVariant NetworkManagerControl::createConnection(const QString &macAddress)
+{
+ /*
+ * Connection settings have the D-Bus signature a{sa{sv}}.
+ * Example of the desired map structure for a new connection:
+ * {
+ * "connection": {
+ * "id": "B2Qt test connection",
+ * "type": "802-3-ethernet"
+ * },
+ * "802-3-ethernet": {
+ * "mac-address": [106, 157, 79, 239, 108, 104]
+ * },
+ * "ipv4": {
+ * "method": "link-local"
+ * }
+ * }
+ */
+
+ QMap<QString, QDBusVariant> connectionMap;
+ connectionMap["id"] = QDBusVariant{QString{"B2Qt connection to %1"}.arg(macAddress)};
+ connectionMap["type"] = QDBusVariant{QStringLiteral("802-3-ethernet")};
+
+ QMap<QString, QDBusVariant> ethernetMap;
+ ethernetMap["mac-address"] = QDBusVariant{macAddressToByteArray(macAddress)};
+
+ QMap<QString, QDBusVariant> ipv4Map;
+ ipv4Map["method"] = QDBusVariant{"link-local"};
+
+ SettingsMap settings;
+ settings["connection"] = connectionMap;
+ settings["802-3-ethernet"] = ethernetMap;
+ settings["ipv4"] = ipv4Map;
+
+ if (!m_bus.isConnected()) {
+ qWarning() << "Could not connect to D-Bus system bus: " << m_bus.lastError();
+ return QVariant{};
+ }
+
+ QDBusInterface connectionSettingsInterface{networkManagerServiceName,
+ networkManagerObjectPath + "/Settings",
+ networkManagerInterfaceName + ".Settings",
+ m_bus};
+
+ if (!connectionSettingsInterface.isValid()) {
+ qWarning() << "Could not find NetworkManager Settings D-Bus interface:"
+ << connectionSettingsInterface.lastError();
+ return QVariant{};
+ }
+
+ const QDBusMessage result = connectionSettingsInterface.call("AddConnection",
+ QVariant::fromValue(settings));
+
+ if (result.type() != QDBusMessage::ReplyMessage)
+ return QVariant{};
+ Q_ASSERT(result.arguments().size() == 1);
+ return result.arguments()[0];
+}
+
+QVariant NetworkManagerControl::findConnectionsByMac(const QString &macAddress)
+{
+ if (!m_bus.isConnected()) {
+ qWarning() << "Could not connect to D-Bus system bus: " << m_bus.lastError();
+ return QVariant{};
+ }
+
+ QDBusInterface settingsInterface{networkManagerServiceName,
+ networkManagerObjectPath + "/Settings",
+ networkManagerInterfaceName + ".Settings",
+ m_bus};
+
+ if (!settingsInterface.isValid()) {
+ qWarning() << "Could not find NetworkManager D-Bus interface:"
+ << settingsInterface.lastError();
+ return QVariant{};
+ }
+
+ QVariant result = settingsInterface.property("Connections");
+ if (!result.isValid()) {
+ qWarning() << "Could not fetch the NetworkManager connections via D-Bus" << settingsInterface.lastError();
+ return QVariant{};
+ }
+
+ const auto connections = result.value<QList<QDBusObjectPath>>();
+ QList<QDBusObjectPath> matchingConnections;
+ std::copy_if(connections.begin(), connections.end(), std::back_inserter(matchingConnections),
+ [&](const QDBusObjectPath &path) {
+ const auto result = connectionMac(m_bus, path);
+ if (!result.isValid())
+ return false;
+ const auto mac = result.toByteArray();
+ return macAddressToByteArray(macAddress) == mac;
+ });
+ qDebug() << "Existing connections for" << macAddress << ":";
+ for (const auto &connection : matchingConnections)
+ qDebug() << " " << connection.path();
+ return QVariant::fromValue(matchingConnections);
+}
+
+QVariant NetworkManagerControl::findNetworkDeviceByMac(const QString &macAddress)
+{
+ QVariant result = listNetworkDevices();
+
+ const auto devices = result.value<QList<QDBusObjectPath>>();
+ for (const auto &device : devices) {
+ QDBusInterface wiredDeviceInterface{networkManagerServiceName,
+ device.path(),
+ networkManagerInterfaceName + ".Device.Wired",
+ m_bus};
+ if (!wiredDeviceInterface.isValid()) {
+ qWarning() << "Could not find NetworkManager Device:" << device.path() << wiredDeviceInterface.lastError();
+ continue;
+ }
+ QVariant macResult = wiredDeviceInterface.property("HwAddress");
+ if (!macResult.isValid()) {
+ const auto error = wiredDeviceInterface.lastError();
+ // Non-wired devices result in InvalidArgs due to no MAC address and need not be warned about
+ if (error.type() != QDBusError::InvalidArgs)
+ qWarning() << "Could not fetch hw address for" << device.path() << error;
+ continue;
+ }
+
+ if (macResult.toString() == macAddress) {
+ qDebug() << macAddress << "is" << device.path();
+ return device.path();
+ }
+ }
+ return QVariant{};
+}
+
+bool NetworkManagerControl::isActivated(const QString &devicePath)
+{
+ if (!m_bus.isConnected()) {
+ qWarning() << "Could not connect to D-Bus system bus: " << m_bus.lastError();
+ // TODO: separate error value?
+ return false;
+ }
+
+ QDBusInterface deviceInterface{networkManagerServiceName,
+ devicePath,
+ networkManagerInterfaceName + ".Device",
+ m_bus};
+ if (!deviceInterface.isValid()) {
+ qWarning() << "Could not find NetworkManager Device:" << devicePath << deviceInterface.lastError();
+ // TODO: separate error value?
+ return false;
+ }
+
+ const QVariant result = deviceInterface.property("State");
+ if (!result.isValid()) {
+ qWarning() << "Could not check activation status of " << devicePath
+ << "via D-Bus:" << deviceInterface.lastError();
+ }
+ const auto state = result.toUInt();
+
+ // Should match https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMDeviceState
+ enum NetworkManagerState {
+ NM_DEVICE_STATE_UNKNOWN = 0,
+ NM_DEVICE_STATE_UNMANAGED = 10,
+ NM_DEVICE_STATE_UNAVAILABLE = 20,
+ NM_DEVICE_STATE_DISCONNECTED = 30,
+ NM_DEVICE_STATE_PREPARE = 40,
+ NM_DEVICE_STATE_CONFIG = 50,
+ NM_DEVICE_STATE_NEED_AUTH = 60,
+ NM_DEVICE_STATE_IP_CONFIG = 70,
+ NM_DEVICE_STATE_IP_CHECK = 80,
+ NM_DEVICE_STATE_SECONDARIES = 90,
+ NM_DEVICE_STATE_ACTIVATED = 100,
+ NM_DEVICE_STATE_DEACTIVATING = 110,
+ NM_DEVICE_STATE_FAILED = 120,
+ };
+
+ switch (state) {
+ case NM_DEVICE_STATE_UNKNOWN:
+ return false;
+ case NM_DEVICE_STATE_UNMANAGED:
+ return false;
+ case NM_DEVICE_STATE_UNAVAILABLE:
+ return false;
+ case NM_DEVICE_STATE_DISCONNECTED:
+ return false;
+ case NM_DEVICE_STATE_PREPARE:
+ return true;
+ case NM_DEVICE_STATE_CONFIG:
+ return true;
+ case NM_DEVICE_STATE_NEED_AUTH:
+ // Authentication is not needed for our configuration
+ return false;
+ case NM_DEVICE_STATE_IP_CONFIG:
+ return true;
+ case NM_DEVICE_STATE_IP_CHECK:
+ return true;
+ case NM_DEVICE_STATE_SECONDARIES:
+ // Secondary devices are not needed for our configuration
+ return false;
+ case NM_DEVICE_STATE_ACTIVATED:
+ return true;
+ case NM_DEVICE_STATE_DEACTIVATING:
+ return false;
+ case NM_DEVICE_STATE_FAILED:
+ return false;
+ default:
+ qCritical() << "Unrecognized NetworkManager device state" << state;
+ return false;
+ }
+}
+
+bool NetworkManagerControl::isConnectionUsingLinkLocal(const QString &connectionPath)
+{
+ const QVariant result = connectionSettings(m_bus, QDBusObjectPath{connectionPath});
+
+ if (!result.isValid()) {
+ // TODO: correct way to handle failure?
+ return false;
+ }
+ const auto settings = result.value<SettingsMap>();
+ return settingsUseLinkLocal(settings);
+}
+
+bool NetworkManagerControl::isDeviceUsingLinkLocal(const QString &devicePath)
+{
+ const QVariant settingsResult = connectionSettingsFromDevice(m_bus, devicePath);
+
+ if (!settingsResult.isValid()) {
+ // TODO: correct way to handle failure?
+ return false;
+ }
+
+ const auto settings = settingsResult.value<SettingsMap>();
+ return settingsUseLinkLocal(settings);
+}
+
+QVariant NetworkManagerControl::listNetworkDevices()
+{
+ if (!m_bus.isConnected()) {
+ qWarning() << "Could not connect to D-Bus system bus: " << m_bus.lastError();
+ return QVariant{};
+ }
+
+ QDBusInterface networkManagerInterface{networkManagerServiceName,
+ networkManagerObjectPath,
+ networkManagerInterfaceName,
+ m_bus};
+
+ if (!networkManagerInterface.isValid()) {
+ qWarning() << "Could not find NetworkManager D-Bus interface:"
+ << networkManagerInterface.lastError();
+ return QVariant{};
+ }
+
+ QVariant result = networkManagerInterface.property("Devices");
+ if (!result.isValid()) {
+ qWarning() << "Could not fetch the NetworkManager devices via D-Bus" << networkManagerInterface.lastError();
+ }
+ return result;
+}
diff --git a/client/networkmanagercontrol.h b/client/networkmanagercontrol.h
new file mode 100644
index 0000000..291d88c
--- /dev/null
+++ b/client/networkmanagercontrol.h
@@ -0,0 +1,47 @@
+/******************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt Debug Bridge.
+**
+** $QT_BEGIN_LICENSE:COMM$
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** $QT_END_LICENSE$
+**
+******************************************************************************/
+#ifndef NETWORKMANAGERCONTROL_H
+#define NETWORKMANAGERCONTROL_H
+
+#include <QtCore/qvariant.h>
+#include <QtDBus/qdbusconnection.h>
+class QDBusObjectPath;
+
+class NetworkManagerControl
+{
+public:
+ NetworkManagerControl();
+
+ bool activateOrCreateConnection(const QDBusObjectPath &devicePath, const QString &macAddress);
+ QVariant createConnection(const QString &macAddress);
+ QVariant findConnectionsByMac(const QString &macAddress);
+ QVariant findNetworkDeviceByMac(const QString &macAddress);
+ bool isActivated(const QString &devicePath);
+ bool isConnectionUsingLinkLocal(const QString &connectionPath);
+ bool isDeviceUsingLinkLocal(const QString &devicePath);
+ QVariant listNetworkDevices();
+
+private:
+ QDBusConnection m_bus;
+};
+
+
+#endif // NETWORKMANAGERCONTROL_H