From ac18d67bf81ef8ae725f524e3662348cad4801f7 Mon Sep 17 00:00:00 2001 From: Ben Francis Date: Fri, 13 Sep 2024 21:52:30 +0100 Subject: [PATCH] WIP: Start on network settings implementation for Ubuntu or Ubuntu Core --- package-lock.json | 14 ++ package.json | 2 + snap/snapcraft.yaml | 1 + src/controllers/settings_controller.ts | 8 +- src/platform.ts | 1 + src/platforms/base.ts | 9 + src/platforms/linux-ubuntu.ts | 298 ++++++++++++++++++++++++- static/js/views/settings.js | 1 + 8 files changed, 331 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 07e35250a..086373add 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4158,6 +4158,12 @@ "integrity": "sha512-maNkQzgLbyoi5mwwAL0KCb6l5/rn02N9y/CIBf4AYPrJ0pU2AvUTQ5LOw6GA4AAPrtiL44LWiwXzybUAUUYA3Q==", "dev": true }, + "@types/dbus": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/dbus/-/dbus-1.0.9.tgz", + "integrity": "sha512-cpXBYPVv6PGXNdb6LfuMgTC4wRFeWSjbkQ4HhwhSkUhmewD1uM/zxWCrmrlwucqpk6iDojlDPXJQ9CAF8+XRpg==", + "dev": true + }, "@types/eslint": { "version": "8.21.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.2.tgz", @@ -7590,6 +7596,14 @@ "integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==", "dev": true }, + "dbus": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/dbus/-/dbus-1.0.7.tgz", + "integrity": "sha512-qba6/ajLoqzCy3Kl3aFgLXLP4TTf0qfgNjib1qoCJG/8HbSs0lDvxkz4nJU63CURZVzxvpK/VpQpT40KA8Kr3A==", + "requires": { + "nan": "^2.14.0" + } + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index fba62d338..fa311a4d7 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "config": "^3.3.4", "country-list": "^2.2.0", "csv-parse": "^4.15.3", + "dbus": "^1.0.7", "express": "^4.17.1", "express-fileupload": "^1.2.1", "express-handlebars": "^5.3.5", @@ -87,6 +88,7 @@ "@types/compression": "^1.7.0", "@types/config": "0.0.38", "@types/country-list": "^2.1.0", + "@types/dbus": "^1.0.9", "@types/event-to-promise": "^0.7.1", "@types/eventsource": "^1.1.6", "@types/express": "^4.17.11", diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index e0d705bba..659df51d2 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -16,6 +16,7 @@ apps: plugs: - network - network-bind + - network-manager parts: python-deps: diff --git a/src/controllers/settings_controller.ts b/src/controllers/settings_controller.ts index 23893f339..9b803fcfb 100644 --- a/src/controllers/settings_controller.ts +++ b/src/controllers/settings_controller.ts @@ -22,6 +22,7 @@ import TunnelService from '../tunnel-service'; import * as CertificateManager from '../certificate-manager'; import pkg from '../package.json'; import { HttpErrorWithCode } from '../errors'; +import { LanMode } from '../platforms/types'; function build(): express.Router { const auth = jwtMiddleware.middleware(); @@ -397,7 +398,12 @@ function build(): express.Router { }); controller.get('/network/lan', auth, (_request, response) => { - if (Platform.implemented('getLanMode')) { + + if (Platform.implemented('getLanModeAsync')) { + Platform.getLanModeAsync().then((mode: LanMode) => { + response.json(mode); + }); + } else if (Platform.implemented('getLanMode')) { response.json(Platform.getLanMode()); } else { response.status(500).send('LAN mode not implemented'); diff --git a/src/platform.ts b/src/platform.ts index 9986c927e..907b889a5 100644 --- a/src/platform.ts +++ b/src/platform.ts @@ -157,6 +157,7 @@ export const setDhcpServerStatus = wrapPlatform(platform, 'setDhcpServe export const getHostname = wrapPlatform(platform, 'getHostname'); export const setHostname = wrapPlatform(platform, 'setHostname'); export const getLanMode = wrapPlatform(platform, 'getLanMode'); +export const getLanModeAsync = wrapPlatform>(platform, 'getLanModeAsync'); export const setLanMode = wrapPlatform(platform, 'setLanMode'); export const getMacAddress = wrapPlatform(platform, 'getMacAddress'); export const getMdnsServerStatus = wrapPlatform(platform, 'getMdnsServerStatus'); diff --git a/src/platforms/base.ts b/src/platforms/base.ts index 724044df8..8dbbe0893 100644 --- a/src/platforms/base.ts +++ b/src/platforms/base.ts @@ -49,6 +49,15 @@ export default class BasePlatform { throw new NotImplementedError('getLanMode'); } + /** + * Get the LAN mode and options asynchronously. + * + * @returns {Promise} {mode: 'static|dhcp|...', options: {...}} + */ + async getLanModeAsync(): Promise { + throw new NotImplementedError('getLanMode'); + } + /** * Set the LAN mode and options. * diff --git a/src/platforms/linux-ubuntu.ts b/src/platforms/linux-ubuntu.ts index 4cbadcb3e..92382c357 100644 --- a/src/platforms/linux-ubuntu.ts +++ b/src/platforms/linux-ubuntu.ts @@ -6,8 +6,302 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { LinuxDebianPlatform } from './linux-debian'; +import BasePlatform from './base'; +import DBus from 'dbus'; +import {LanMode} from './types'; -class LinuxUbuntuPlatform extends LinuxDebianPlatform {} +class LinuxUbuntuPlatform extends BasePlatform { + + systemBus = DBus.getBus('system'); + + /** + * Get a list of network adapters from the system network manager. + * + * @returns {Array} An array of DBus object paths. + */ + private getDevices(): Promise { + return new Promise((resolve, reject) => { + this.systemBus.getInterface('org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager', + 'org.freedesktop.NetworkManager', + function(error, iface) { + if (error) { + console.error('Error accessing the NetworkManager DBus interface: ' + error); + reject(); + } + iface.GetAllDevices(function(error: Error, result: string[]) { + if (error) { + console.error('Error calling GetAllDevices on NetworkManager DBus: ' + error); + reject(); + } + resolve(result); + }); + }); + }); + } + + /** + * Get the device type for a given network adapter. + * + * @param {String} path Object path for device. + * @returns {Promise} Resolves with a device type + * (1 is Ethernet, 2 is Wi-Fi...). + */ + private getDeviceType(path: string) { + return new Promise((resolve, reject) => { + this.systemBus.getInterface('org.freedesktop.NetworkManager', + path, + 'org.freedesktop.NetworkManager.Device', + function(error, iface) { + if (error) { + console.error(error); + reject(); + } + iface.getProperty('DeviceType', function(error, value) { + if (error) { + console.error(error); + reject(); + } + resolve(value); + }); + }); + }); + } + + /** + * Get a list of network Ethernet adapters from the system network manager. + * + * @returns {Promise>} A promise which resolves with an array + * of DBus object paths. + */ + private async getEthernetDevices(): Promise { + // Get a list of all network adapter devices + let devices = await this.getDevices(); + let ethernetDevices: string[] = []; + // Filter by type + for (const device of devices) { + const type = await this.getDeviceType(device); + if (type == 1) { + ethernetDevices.push(device); + } + } + return ethernetDevices; + } + + /** + * Get the active connection associated with a device. + * + * @param {String} path Object path for device. + * @returns {Promise} Resolves with object path of the active + * connection object associated with this device. + */ + private getDeviceConnection(path: string): Promise { + return new Promise((resolve, reject) => { + const systemBus = this.systemBus; + systemBus.getInterface('org.freedesktop.NetworkManager', + path, + 'org.freedesktop.NetworkManager.Device', + function(error, iface) { + if (error) { + console.error(error); + reject(); + } + iface.getProperty('ActiveConnection', function(error, activeConnectionPath) { + if (error) { + console.error(error); + reject(); + } + systemBus.getInterface('org.freedesktop.NetworkManager', + activeConnectionPath, + 'org.freedesktop.NetworkManager.Connection.Active', + function(error, iface) { + if (error) { + console.error(error); + reject(); + } + iface.getProperty('Connection', function(error, value) { + if (error) { + console.error(error); + reject(); + } + resolve(value); + }); + }); + }); + }); + }); + } + + /** + * Get the settings for a given connection. + * + * @param {String} path Object path for a connection settings profile. + * @returns {Promise} Resolves with the settings of a connection. + */ + private getConnectionSettings(path: string) { + return new Promise((resolve, reject) => { + this.systemBus.getInterface('org.freedesktop.NetworkManager', + path, + 'org.freedesktop.NetworkManager.Settings.Connection', + function(error, iface) { + if (error) { + console.error(error); + reject(); + } + iface.GetSettings(function(error: Error, value: any) { + if (error) { + console.error(error); + reject(); + } + resolve(value); + }); + }); + }); + } + + /** + * Get the LAN mode and options. + * + * @returns {Promise} Promise that resolves with + * {mode: 'static|dhcp|...', options: {...}} + */ + async getLanModeAsync(): Promise { + let result: LanMode = { + mode: '', + options: {} + }; + return this.getEthernetDevices().then((devices) => { + return this.getDeviceConnection(devices[0]); + }).then((connection) => { + return this.getConnectionSettings(connection); + }).then((settings: any) => { + if(settings.ipv4.method == 'auto') { + result.mode = 'dhcp'; + } else if(settings.ipv4.method == 'manual') { + result.mode = 'static'; + } + return result; + }).catch((error) => { + console.error('Error getting LAN mode from Network Manager: ' + error); + // TODO: Throw error instead? + return result; + }); + } + + // Currently unused code... + + /** + * Get the path to the IPv4 configuration for a given network adapter. + * + * @param {String} path Object path for a device. + * @returns {Promise} Promise resolves with path to configuration object. + */ + /*getDeviceIp4ConfigPath(path: string) { + return new Promise((resolve, reject) => { + this.systemBus.getInterface('org.freedesktop.NetworkManager', + path, + 'org.freedesktop.NetworkManager.Device', + function(error, iface) { + if (error) { + console.error(error); + reject(); + } + iface.getProperty('Ip4Config', function(error, value) { + if (error) { + console.error(error); + reject(); + } + resolve(value); + }); + }); + }); + }*/ + + /** + * Get the DHCP configuration for a given network adapter. + * + * @param {String} path Object path for device. + * @returns {Promise} Promise resolves with configuration. + */ + /*getDeviceDHCP4Config(path: string) { + return new Promise((resolve, reject) => { + let systemBus = this.systemBus; + systemBus.getInterface('org.freedesktop.NetworkManager', + path, + 'org.freedesktop.NetworkManager.Device', + function(error, iface) { + if (error) { + console.error(error); + reject(); + } + iface.getProperty('Dhcp4Config', function(error, dhcpPath) { + if (error) { + console.error(error); + reject(); + } + systemBus.getInterface('org.freedesktop.NetworkManager', + dhcpPath, + 'org.freedesktop.NetworkManager.DHCP4Config', + function(error, iface) { + if (error) { + console.error(error); + reject(); + } + iface.getProperty('Options', function(error, value) { + if (error) { + console.error(error); + reject(); + } + resolve(value); + }); + }); + }); + }); + }); + }*/ + + /** + * Get a list of network connection configurations from the system network manager. + * + * @returns {Array} An array of DBus object paths for connection configurations. + */ + /*listConnections(): Promise { + return new Promise((resolve, reject) => { + this.systemBus.getInterface('org.freedesktop.NetworkManager', + '/org/freedesktop/NetworkManager/Settings', + 'org.freedesktop.NetworkManager.Settings', + function(error, iface) { + if (error) { + console.error('Error accessing the NetworkManager Settings DBus interface: ' + error); + reject(); + } + iface.ListConnections(function(error: Error, result: string[]) { + if (error) { + console.error('Error calling ListConnections on NetworkManager Settings DBus interface: ' + error); + reject(); + } + resolve(result); + }); + }); + }); + }*/ + + // Testing... + /*getLanMode(): LanMode { + this.getEthernetDevices().then((devices) => { + return this.getDeviceConnection(devices[0]); + }).then((connection) => { + return this.getConnectionSettings(connection); + }).then((settings) => { + console.log('Connection settings for eth0 are probably:'); + console.dir(settings); + }); + + return { + mode: 'dhcp', + options: {} + } + }*/ +} export default new LinuxUbuntuPlatform(); diff --git a/static/js/views/settings.js b/static/js/views/settings.js index 8fe304809..556587903 100644 --- a/static/js/views/settings.js +++ b/static/js/views/settings.js @@ -836,6 +836,7 @@ const SettingsScreen = { switch (body.os) { case 'linux-debian': case 'linux-raspbian': + case 'linux-ubuntu': this.elements.network.client.main.classList.remove('hidden'); break; default: