summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLukas Märdian <slyon@ubuntu.com>2021-03-08 17:44:18 +0100
committerGitHub <noreply@github.com>2021-03-08 17:44:18 +0100
commit2263a13439eff189a6cd2cf9fbeede7d52a2ab5a (patch)
tree549ed5fdd685022a2f04e0476e23dfbcb66056f4 /src
parentd34df503a977e6052e63c5cdc271419d80fcfcd1 (diff)
libnetplan: provide API for NetworkManager YAML backend (#193)
fix match.original_name vs netdef->id shortcut (it shall be the same value, but not same reference/pointer) Tracking of filename per netdef in the YAML parser Improve wake-on-lan handling Use NM aliases for wifi/ethernet keyfile groups Refactoring NetworkManager keyfile generator to use GKeyFile instead of GString Adding a NetworkManager keyfile parser Adding a netplan YAML generator New APIs introduced: // YAML generator void write_netplan_conf(const NetplanNetDefinition* def, const char* rootdir); // NM keyfile parser gboolean netplan_parse_keyfile(const char* filename, GError** error); // general & utils guint netplan_clear_netdefs(); gboolean netplan_delete_connection(const char* id, const char* rootdir); gboolean netplan_generate(const char* rootdir); gchar* netplan_get_id_from_nm_filename(const char* filename, const char* ssid); == COMMITS: == * Prepare for NetworkManager YAML backend functionality (#181) This is the first in a series of pull request to be prepared for setting up the functionality for a NetworkManager YAML/netplan backend, to be merged into slyon/networkmanager-yaml-backend before it will collectively be merged into master. This PR refactors some of the YAML processing logic into libnetplan (from the generate binary), keeps track of the filename (absolute path) of the YAML file for each NetplanNetDefinition and implements a first additional library function netplan_get_id_from_nm_filename, which will output the NetplanNetDefinition ID given a path to a netplan generated .nmconnection keyfile in /run/NetworkManager/system-connections/. The netplan ID is not stored inside NM's data structures, therefore we need a way to gain netplan's identifier for any given connection profile. Commits: * parse:generate: refactor process_yaml_hierarchy * parse: track filename of netdefs * parse: add netplan_get_id_from_filename library function * parse: fix typo * generate: some stylistic cleanup * util: move netplan_get_id_from_nm_filename from parse.c * Implement netplan_generate and netplan_delete_connection APIs (#182) This is 2nd in a series of pull request implementing the functionality in libnetplan to provide a NetworkManager YAML backend. It builds upon #181 * It improves the netplan set CLI a bit, to delete a file, if only network: {version: 2} is left. * It implements a netplan_generate function, which will call netplan generate in the background by spawning another process. * At some point the generate binary should be refactored, to call functions of libnetplan instead, so we could use this same (refactored) functionality inside the library directly * But for now this is as good as it gets... * It implements a netplan_delete_connection function, which will delete a connection from the YAML structure of a file (or potentially the whole file, if it is empty afterwards), by utilizing the netplan set network.TYPE.IFNAME=NULL functionality by spawning another process. * At some point we should get rid of this Python/C split of the YAML parsers, to have all the functionality available inside the C library. * But for now this is as good as it gets, without duplicating any logic... * NM: refactor keyfile generator, by using GLib's keyfile writer instead of custom writer (#184) This is the 3rd in a series of pull requests to enable the implementation of a YAML/netplan backend for NetworkManager. In this PR netplan's current keyfile generation code (in nm.c) is refactored to make use of GLib's keyfile writer, instead of using a custom, home grown approach. This way the keyfile settings can easily be read, written and overwritten (e.g. by the keyfile passthrough/fallback settings in #183) and it should be more robust overall. While on it: Clean up the usage of keyfile alias for ethernet, wifi and wifi-security, which NetworkManager writes by default since a long time. Commits: * nm: refactoring to use GKeyFile writer, instead of home grown * nm: uses alias for 'ethernet', 'wifi' and 'wifi-security' NetworkManager writes the alias for 'ethernet' (802-3-ethernet), 'wifi' (802-11-wireless) and 'wifi-security' (802-11-wireless-security) settings by default since a long time, we should do so as well. Especially we should not mix and match. see: https://bugzilla.gnome.org/show_bug.cgi?id=696940 https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/commit/c36200a225aefb2a3919618e75682646899b82c0 nm_keyfile_plugin_kf_set* from nm-keyfile-utils.c (libnm-core) * Prepare passthrough mode for NM backend and YAML serializer (#187) This is 4th in a series of pull requests. It enables parsing of the passthrough keyword inside the networkmanager backend settings, inside any netdef or wifi AP. Furthermore, it add a new serialize.c/h module, which can take a single netdef and render it into a YAML file. Testing is implemented by rendering a given YAML file into a new YAML file, and comparing the 1:1 match. The YAML serializer does (by far) not yet support all supported netplan settings, but only the minimal set to enable the NetworkManager backend via passthrough mode. The NM backend settings need to be available in every wifi AP definition (in addition to the netdef itself), as in the NetworkManager world, each wifi connection is a separate connection profile, while in the netplan world the wifi networks/AP are combined under a single device/netdef. But the settings passthrough (i.e. fallback mode) needs to be available for each individual NM connection profile, therefore we need them in the AP struct. The passthrough setting is a mapping inside backend specific settings of key-value pairs, where the keys are separated by a dot in the format KEYFILE_GROUP.KEYFILE_KEY. Those values are passed through to the keyfile as is, and can be used as a fallback mechanism, where a specific feature is not yet implemented. * Implement NM keyfile passthrough mode as a fallback mechanism (#183) This is 5th in a series of pull requests to prepare for the NetworkManager YAML backend. It builds upon #187 It introduces a new (undocumented) top level type others, which needs to be used when a NM connection of unsupported type needs to be written. If used the connection.type needs to be specified by a passthrough setting, e.g.: network: version: 2 others: renderer: NetworkManager networkmanager: uuid: ... passthrough: connection.type: vxlan ipv4.method: auto ... others is undocumented on purpose, as this is not supposed to be used in regular netplan configs, but can be used as a fallback mechanism, if a given network connection type is not yet implemented in netplan. It can then still be used via passthrough mode. Furthermore, this PR introduced a new module nm-keyfile to libnetplan, which contains some logic to help with integration of the NetworkManager YAML settings backend. Especially the netplan_render_yaml_from_nm_keyfile function, which transforms a given GKeyFile* structure into a valid NetplanNetDefinition* structure and uses the serializer to transform this into a valid netplan YAML and save it to disk. For now this makes heavy use the the passthrough fallback mechanism, but it will be extended to place keyfile settings into the correct netplan schema in the future, step by step. The nm.c module was extended to make use of the passthrough fallback mechanism, to render a valid NetworkManager keyfile out of this data and write it to disk. It uses the internal netplan data structure to generate the keyfile as usual, but allows to extend (or override) specified settings via passthrough if they are not (yet/fully) supported. As discussed in #181, the netplan_get_id_from_nm_filename function is moved into this new nm-keyfile module from utils as this might be a better place for keyfile specific functionality. Currently this PR is based upon the slyon/nm-4 branch. It will be rebased once PR #187 is merged, but the underlying base shouldn't change too much, so it should be fine to review this already. Commits: * nm-keyfile: Add netplan_render_yaml_from_nm_keyfile API * configmanager: handle 'others' key * nm: Implement fallback/passthrough mode * nm-keyfile: support wifis, modems, bridges in addition to ethernets (and others) * nm-keyfile: cleanup * nm-keyfile: support bonds, vlans, tunnels * validation: check others vs passthrough * nm: fallback generator: allow dotted groups (i.e. wireguard-peer) * nm: Update copyright * nm-keyfile: handle connection-type alias * WIP: nm: fallback integration * nm-keyfile: modularize and clear handled fallback keys * WIP: nm: fallback wifi_mode & interface_name * WIP: netplan-yaml: draft YAML export * NM: split into keyfile parser and YAML serializer * nm-keyfile: cleanup * nm: cleanup * parse: cleanup * nm: mention overriden settings in keyfile comment * nm: allow to specify name (NM id) from backend_settings * nm: simplify connection.type error handling * test_nm_backend: adopt tests * nm-keyfile: move netplan_get_id_from_nm_filename from util * tests: move generator tests into generator/test_passthrough.py * nm-keyfile: clear netdefs and improve docs * nm: improve comments/docs * nm-keyfile: support canonical names in addition to alias * nm-keyfile: improve formatting * doc: mention the device type * Improvements for the NetworkManager YAML backend integration (#189) This PR is 6th in a series of pull requests to prepare for the NetworkManager YAML backend. It builds upon #183 and contains a few improvements and fixes, enabling the NetworkManager test-suite (i.e. make check) to fully pass, especially the tests in the patched keyfile plugin (src/settings/plugins/keyfile/tests/test-keyfile-settings). It implements the following functionality: Adding some basic support for the modems YAML schema, to allow matching of (physical) modem interfaces and enable the detection of GSM vs CDMA connections, which are distinct in NM while using the same definition in Netplan. Switching the passthrough key-value pairs to a DataList (instead of HashTable), to keep order of the keyfile elements, which is relevant in some cases (e.g. for tc.qdiscs) Avoid parsing and serialization of type other connections, as they might not contain the relevant handlers. Use full passthrough mode for those connections, to avoid parsing failures. Allow independent modification of Netdef ID & match.name, to enable changing the connection.interface-name from nmcli, while keeping the netdef ID equal to a previously existing ID. This enables overriding of existing netdef ID by 90-NM-... YAML file, keeping the same Netdef ID. Implement handling of keyfile UUIDs for each NM connection profile Implement handling of empty keyfile groups (i.e. relevant for [bridge]) Commits: * nm:nm-keyfile: allow to define empty Keyfile groups, like [bridge] or [proxy] * passthrough: switch from GHashTable to GDataList, as some of the keyfile values need to be ordered, like tc.qdisks or tc.filters * nm: special handling for [tc] group, where keys can contain dots * serialize: write match stanza only for physical devices * parse: modems are physical links and can have a 'match' stanza * parse: improve clearing of netdefs * nm: improve annotation of passthrouh settings * nm-keyfile: improve removal of supported keys Also, removes the group, if all of its keys were removed * nm: improve detection of GSM connection GSM/CDMA can also be defined on a type=bluetooth connection – not just on MODEMS connections * nm:nm-keyfile:serialize: set wakeonlan only for ethernet devices * nm-keyfile:serialize: some modem stanzas * nm-keyfile:serialize: handle OTHER type via full passthrough mode * nm:nm-keyfile:serialize: improve interface-name/match handling * nm: improve handling of NM uuid * nm: improve passthrough.connection.type handling * nm: improve handling of wifi.mode * tests: add and adopt test cases * nm-keyfile: allow to pass a previously known netdef ID, to enable overrides * parse: allow changing match.name independenly of netdef ID * nm-keyfile: update GSM vs CDMA comment * parse: avoid creation of 'netdefs' hashmap in netplan_parse_yaml * NM Integration: API update & Parser/Generator split (#191) This is 7th in a series of pull requests to provide the API needed for implementation of a NetworkManager YAML backend. It builds upon #189 Changes contained in this PR: * Split the code/modules into a YAML/netplan generator (netplan.o in addition to nm.o, networkd.o, openvswitch.o, ...) * Split the code/modules into a keyfile/NM parser (parse-nm.o in addition to our YAML/netplan parser in parse.o) * Rename others top-level device type/stanza to nm-devices to make it more explicit and print a warning when using it * Move netplan_get_id_from_nm_filename back to utils.o, as it does not fit into the parse-nm.o module anymore (it is no parser specific functionality). Apply a small improvement to detect netplan_ids in testing scenario, where rootdir is provided. * Unify the new netplan generator module API with the other generators: void write_netplan_conf(const NetplanNetDefinition* def, const char* rootdir) * Unify the new keyfile parser module API with the other parser: gboolean netplan_parse_keyfile(const char* filename, GError** error) * cleanup Co-authored-by: Łukasz Zemczak <sil2100@vexillium.org>
Diffstat (limited to 'src')
-rw-r--r--src/generate.c40
-rw-r--r--src/netplan.c209
-rw-r--r--src/netplan.h93
-rw-r--r--src/networkd.c4
-rw-r--r--src/nm.c633
-rw-r--r--src/parse-nm.c275
-rw-r--r--src/parse-nm.h22
-rw-r--r--src/parse.c154
-rw-r--r--src/parse.h47
-rw-r--r--src/util.c96
-rw-r--r--src/util.h3
-rw-r--r--src/validation.c3
12 files changed, 1228 insertions, 351 deletions
diff --git a/src/generate.c b/src/generate.c
index 40d6c87..d820f35 100644
--- a/src/generate.c
+++ b/src/generate.c
@@ -171,18 +171,6 @@ exit_find:
return ret;
}
-static void
-process_input_file(const char* f)
-{
- GError* error = NULL;
-
- g_debug("Processing input file %s..", f);
- if (!netplan_parse_yaml(f, &error)) {
- g_fprintf(stderr, "%s\n", error->message);
- exit(1);
- }
-}
-
int main(int argc, char** argv)
{
GError* error = NULL;
@@ -225,27 +213,8 @@ int main(int argc, char** argv)
if (files && !called_as_generator) {
for (gchar** f = files; f && *f; ++f)
process_input_file(*f);
- } else {
- /* Files with asciibetically higher names override/append settings from
- * earlier ones (in all config dirs); files in /run/netplan/
- * shadow files in /etc/netplan/ which shadow files in /lib/netplan/.
- * To do that, we put all found files in a hash table, then sort it by
- * file name, and add the entries from /run after the ones from /etc
- * and those after the ones from /lib. */
- if (find_yaml_glob(rootdir, &gl) != 0)
- return 1; // LCOV_EXCL_LINE
- /* keys are strdup()ed, free them; values point into the glob_t, don't free them */
- g_autoptr(GHashTable) configs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
- g_autoptr(GList) config_keys = NULL;
-
- for (size_t i = 0; i < gl.gl_pathc; ++i)
- g_hash_table_insert(configs, g_path_get_basename(gl.gl_pathv[i]), gl.gl_pathv[i]);
-
- config_keys = g_list_sort(g_hash_table_get_keys(configs), (GCompareFunc) strcmp);
-
- for (GList* i = config_keys; i != NULL; i = i->next)
- process_input_file(g_hash_table_lookup(configs, i->data));
- }
+ } else if (!process_yaml_hierarchy(rootdir))
+ return 1; // LCOV_EXCL_LINE
netdefs = netplan_finish_parse(&error);
if (error) {
@@ -259,16 +228,15 @@ int main(int argc, char** argv)
cleanup_ovs_conf(rootdir);
cleanup_sriov_conf(rootdir);
- if (mapping_iface && netdefs) {
+ if (mapping_iface && netdefs)
return find_interface(mapping_iface);
- }
/* Generate backend specific configuration files from merged data. */
+ write_ovs_conf_finish(rootdir); // OVS cleanup unit is always written
if (netdefs) {
g_debug("Generating output files..");
g_list_foreach (netdefs_ordered, nd_iterator_list, rootdir);
write_nm_conf_finish(rootdir);
- write_ovs_conf_finish(rootdir);
if (any_sriov) write_sriov_conf_finish(rootdir);
/* We may have written .rules & .link files, thus we must
* invalidate udevd cache of its config as by default it only
diff --git a/src/netplan.c b/src/netplan.c
new file mode 100644
index 0000000..73d6551
--- /dev/null
+++ b/src/netplan.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 Canonical, Ltd.
+ * Author: Lukas Märdian <slyon@ubuntu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <yaml.h>
+
+#include "netplan.h"
+#include "parse.h"
+
+static gboolean
+write_match(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
+{
+ YAML_SCALAR_PLAIN(event, emitter, "match");
+ YAML_MAPPING_OPEN(event, emitter);
+ YAML_STRING(event, emitter, "name", def->match.original_name);
+ YAML_MAPPING_CLOSE(event, emitter);
+ return TRUE;
+error: return FALSE; // LCOV_EXCL_LINE
+}
+
+typedef struct {
+ yaml_event_t* event;
+ yaml_emitter_t* emitter;
+} _passthrough_handler_data;
+
+static void
+_passthrough_handler(GQuark key_id, gpointer value, gpointer user_data)
+{
+ _passthrough_handler_data *d = user_data;
+ const gchar* key = g_quark_to_string(key_id);
+ YAML_SCALAR_PLAIN(d->event, d->emitter, key);
+ YAML_SCALAR_QUOTED(d->event, d->emitter, value);
+error: return; // LCOV_EXCL_LINE
+}
+
+static gboolean
+write_backend_settings(yaml_event_t* event, yaml_emitter_t* emitter, NetplanBackendSettings s) {
+ if (s.nm.uuid || s.nm.name || s.nm.passthrough) {
+ YAML_SCALAR_PLAIN(event, emitter, "networkmanager");
+ YAML_MAPPING_OPEN(event, emitter);
+ if (s.nm.uuid) {
+ YAML_SCALAR_PLAIN(event, emitter, "uuid");
+ YAML_SCALAR_PLAIN(event, emitter, s.nm.uuid);
+ }
+ if (s.nm.name) {
+ YAML_SCALAR_PLAIN(event, emitter, "name");
+ YAML_SCALAR_QUOTED(event, emitter, s.nm.name);
+ }
+ if (s.nm.passthrough) {
+ YAML_SCALAR_PLAIN(event, emitter, "passthrough");
+ YAML_MAPPING_OPEN(event, emitter);
+ _passthrough_handler_data d;
+ d.event = event;
+ d.emitter = emitter;
+ g_datalist_foreach(&s.nm.passthrough, _passthrough_handler, &d);
+ YAML_MAPPING_CLOSE(event, emitter);
+ }
+ YAML_MAPPING_CLOSE(event, emitter);
+ }
+ return TRUE;
+error: return FALSE; // LCOV_EXCL_LINE
+}
+
+static gboolean
+write_access_points(yaml_event_t* event, yaml_emitter_t* emitter, const NetplanNetDefinition* def)
+{
+ NetplanWifiAccessPoint* ap = NULL;
+ GHashTableIter iter;
+ gpointer key, value;
+ YAML_SCALAR_PLAIN(event, emitter, "access-points");
+ YAML_MAPPING_OPEN(event, emitter);
+ g_hash_table_iter_init(&iter, def->access_points);
+ while (g_hash_table_iter_next(&iter, &key, &value)) {
+ ap = value;
+ YAML_SCALAR_QUOTED(event, emitter, ap->ssid);
+ YAML_MAPPING_OPEN(event, emitter);
+ if (ap->hidden) {
+ YAML_SCALAR_PLAIN(event, emitter, "hidden");
+ YAML_SCALAR_PLAIN(event, emitter, "true");
+ }
+ YAML_SCALAR_PLAIN(event, emitter, "mode");
+ if (ap->mode != NETPLAN_WIFI_MODE_OTHER) {
+ YAML_SCALAR_PLAIN(event, emitter, netplan_wifi_mode_to_str[ap->mode]);
+ } else {
+ // LCOV_EXCL_START
+ g_warning("netplan: serialize: %s (SSID %s), unsupported AP mode, falling back to 'infrastructure'", def->id, ap->ssid);
+ YAML_SCALAR_PLAIN(event, emitter, "infrastructure"); //TODO: add YAML comment about unsupported mode
+ // LCOV_EXCL_STOP
+ }
+ if (!write_backend_settings(event, emitter, ap->backend_settings)) goto error;
+ YAML_MAPPING_CLOSE(event, emitter);
+ }
+ YAML_MAPPING_CLOSE(event, emitter);
+ return TRUE;
+error: return FALSE; // LCOV_EXCL_LINE
+}
+
+/**
+ * Generate the Netplan YAML configuration for the selected netdef
+ * @def: NetplanNetDefinition (as pointer), the data to be serialized
+ * @rootdir: If not %NULL, generate configuration in this root directory
+ * (useful for testing).
+ */
+void
+write_netplan_conf(const NetplanNetDefinition* def, const char* rootdir)
+{
+ g_autofree gchar *filename = NULL;
+ g_autofree gchar *path = NULL;
+
+ /* NetworkManager produces one file per connection profile
+ * It's 90-* to be higher priority than the default 70-netplan-set.yaml */
+ if (def->backend_settings.nm.uuid)
+ filename = g_strconcat("90-NM-", def->backend_settings.nm.uuid, ".yaml", NULL);
+ else
+ filename = g_strconcat("10-netplan-", def->id, ".yaml", NULL);
+ path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, "etc", "netplan", filename, NULL);
+
+ /* Start rendering YAML output */
+ yaml_emitter_t emitter_data;
+ yaml_event_t event_data;
+ yaml_emitter_t* emitter = &emitter_data;
+ yaml_event_t* event = &event_data;
+ FILE *output = fopen(path, "wb");
+
+ YAML_OUT_START(event, emitter, output);
+ /* build the netplan boilerplate YAML structure */
+ YAML_SCALAR_PLAIN(event, emitter, "network");
+ YAML_MAPPING_OPEN(event, emitter);
+ // TODO: global backend/renderer
+ YAML_STRING_PLAIN(event, emitter, "version", "2");
+ YAML_SCALAR_PLAIN(event, emitter, netplan_def_type_to_str[def->type]);
+ YAML_MAPPING_OPEN(event, emitter);
+ YAML_SCALAR_PLAIN(event, emitter, def->id);
+ YAML_MAPPING_OPEN(event, emitter);
+ YAML_STRING_PLAIN(event, emitter, "renderer", netplan_backend_to_name[def->backend])
+
+ if (def->type == NETPLAN_DEF_TYPE_NM)
+ goto only_passthrough; //do not try to handle "unknown" connection types
+
+ if (def->has_match)
+ write_match(event, emitter, def);
+
+ /* wake-on-lan */
+ if (def->wake_on_lan)
+ YAML_STRING_PLAIN(event, emitter, "wakeonlan", "true");
+
+ /* some modem settings to auto-detect GSM vs CDMA connections */
+ if (def->modem_params.auto_config)
+ YAML_STRING_PLAIN(event, emitter, "auto-config", "true");
+ YAML_STRING(event, emitter, "apn", def->modem_params.apn);
+ YAML_STRING(event, emitter, "device-id", def->modem_params.device_id);
+ YAML_STRING(event, emitter, "network-id", def->modem_params.network_id);
+ YAML_STRING(event, emitter, "pin", def->modem_params.pin);
+ YAML_STRING(event, emitter, "sim-id", def->modem_params.sim_id);
+ YAML_STRING(event, emitter, "sim-operator-id", def->modem_params.sim_operator_id);
+
+ if (def->type == NETPLAN_DEF_TYPE_WIFI)
+ if (!write_access_points(event, emitter, def)) goto error;
+only_passthrough:
+ if (!write_backend_settings(event, emitter, def->backend_settings)) goto error;
+
+ /* Close remaining mappings */
+ YAML_MAPPING_CLOSE(event, emitter);
+ YAML_MAPPING_CLOSE(event, emitter);
+ YAML_MAPPING_CLOSE(event, emitter);
+
+ /* Tear down the YAML emitter */
+ YAML_OUT_STOP(event, emitter);
+ fclose(output);
+ return;
+
+ // LCOV_EXCL_START
+error:
+ yaml_emitter_delete(emitter);
+ fclose(output);
+ // LCOV_EXCL_STOP
+}
+
+/* XXX: implement the following functions, once needed:
+void write_netplan_conf_finish(const char* rootdir)
+void cleanup_netplan_conf(const char* rootdir)
+*/
+
+/**
+ * Helper function for testing only
+ */
+void
+_write_netplan_conf(const char* netdef_id, const char* rootdir)
+{
+ GHashTable* ht = NULL;
+ const NetplanNetDefinition* def = NULL;
+ ht = netplan_finish_parse(NULL);
+ def = g_hash_table_lookup(ht, netdef_id);
+ write_netplan_conf(def, rootdir);
+}
diff --git a/src/netplan.h b/src/netplan.h
new file mode 100644
index 0000000..d2c538b
--- /dev/null
+++ b/src/netplan.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2021 Canonical, Ltd.
+ * Author: Lukas Märdian <slyon@ubuntu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "parse.h"
+
+#define YAML_MAPPING_OPEN(event_ptr, emitter_ptr) \
+{ \
+ yaml_mapping_start_event_initialize(event_ptr, NULL, (yaml_char_t *)YAML_MAP_TAG, 1, YAML_ANY_MAPPING_STYLE); \
+ if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \
+}
+#define YAML_MAPPING_CLOSE(event_ptr, emitter_ptr) \
+{ \
+ yaml_mapping_end_event_initialize(event_ptr); \
+ if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \
+}
+#define YAML_SCALAR_PLAIN(event_ptr, emitter_ptr, scalar) \
+{ \
+ yaml_scalar_event_initialize(event_ptr, NULL, (yaml_char_t *)YAML_STR_TAG, (yaml_char_t *)scalar, strlen(scalar), 1, 0, YAML_PLAIN_SCALAR_STYLE); \
+ if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \
+}
+/* Implicit plain and quoted tags, double quoted style */
+#define YAML_SCALAR_QUOTED(event_ptr, emitter_ptr, scalar) \
+{ \
+ yaml_scalar_event_initialize(event_ptr, NULL, (yaml_char_t *)YAML_STR_TAG, (yaml_char_t *)scalar, strlen(scalar), 1, 1, YAML_DOUBLE_QUOTED_SCALAR_STYLE); \
+ if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \
+}
+#define YAML_STRING(event_ptr, emitter_ptr, key, value_ptr) \
+{ \
+ if (value_ptr) { \
+ YAML_SCALAR_PLAIN(event, emitter, key); \
+ YAML_SCALAR_QUOTED(event, emitter, value_ptr); \
+ } \
+}
+#define YAML_STRING_PLAIN(event_ptr, emitter_ptr, key, value_ptr) \
+{ \
+ if (value_ptr) { \
+ YAML_SCALAR_PLAIN(event, emitter, key); \
+ YAML_SCALAR_PLAIN(event, emitter, value_ptr); \
+ } \
+}
+/* open YAML emitter, document, stream and initial mapping */
+#define YAML_OUT_START(event_ptr, emitter_ptr, file) \
+{ \
+ yaml_emitter_initialize(emitter_ptr); \
+ yaml_emitter_set_output_file(emitter_ptr, file); \
+ yaml_stream_start_event_initialize(event_ptr, YAML_UTF8_ENCODING); \
+ if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \
+ yaml_document_start_event_initialize(event_ptr, NULL, NULL, NULL, 1); \
+ if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \
+ YAML_MAPPING_OPEN(event_ptr, emitter_ptr); \
+}
+/* close initial YAML mapping, document, stream and emitter */
+#define YAML_OUT_STOP(event_ptr, emitter_ptr) \
+{ \
+ YAML_MAPPING_CLOSE(event_ptr, emitter_ptr); \
+ yaml_document_end_event_initialize(event_ptr, 1); \
+ if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \
+ yaml_stream_end_event_initialize(event_ptr); \
+ if (!yaml_emitter_emit(emitter_ptr, event_ptr)) goto error; \
+ yaml_emitter_delete(emitter_ptr); \
+}
+
+static const char* const netplan_def_type_to_str[NETPLAN_DEF_TYPE_MAX_] = {
+ [NETPLAN_DEF_TYPE_NONE] = NULL,
+ [NETPLAN_DEF_TYPE_ETHERNET] = "ethernets",
+ [NETPLAN_DEF_TYPE_WIFI] = "wifis",
+ [NETPLAN_DEF_TYPE_MODEM] = "modems",
+ [NETPLAN_DEF_TYPE_VIRTUAL] = NULL,
+ [NETPLAN_DEF_TYPE_BRIDGE] = "bridges",
+ [NETPLAN_DEF_TYPE_BOND] = "bonds",
+ [NETPLAN_DEF_TYPE_VLAN] = "vlans",
+ [NETPLAN_DEF_TYPE_TUNNEL] = "tunnels",
+ [NETPLAN_DEF_TYPE_PORT] = NULL,
+ [NETPLAN_DEF_TYPE_NM] = "nm-devices",
+};
+
+void write_netplan_conf(const NetplanNetDefinition* def, const char* rootdir);
diff --git a/src/networkd.c b/src/networkd.c
index d548c35..05a6bf9 100644
--- a/src/networkd.c
+++ b/src/networkd.c
@@ -976,8 +976,8 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir)
case NETPLAN_WIFI_MODE_ADHOC:
g_string_append(s, " mode=1\n");
break;
- case NETPLAN_WIFI_MODE_AP:
- g_fprintf(stderr, "ERROR: %s: networkd does not support wifi in access point mode\n", def->id);
+ default:
+ g_fprintf(stderr, "ERROR: %s: %s: networkd does not support this wifi mode\n", def->id, ap->ssid);
exit(1);
}
diff --git a/src/nm.c b/src/nm.c
index 6772523..2049c19 100644
--- a/src/nm.c
+++ b/src/nm.c
@@ -1,6 +1,7 @@
/*
- * Copyright (C) 2016 Canonical, Ltd.
+ * Copyright (C) 2016-2021 Canonical, Ltd.
* Author: Martin Pitt <martin.pitt@ubuntu.com>
+ * Author: Lukas Märdian <slyon@ubuntu.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -29,10 +30,10 @@
#include "parse.h"
#include "util.h"
#include "validation.h"
+#include "parse-nm.h"
GString* udev_rules;
-
/**
* Append NM device specifier of @def to @s.
*/
@@ -79,10 +80,13 @@ g_string_append_netdef_match(GString* s, const NetplanNetDefinition* def)
static const gboolean
modem_is_gsm(const NetplanNetDefinition* def)
{
- if (def->type == NETPLAN_DEF_TYPE_MODEM && (def->modem_params.apn ||
- def->modem_params.auto_config || def->modem_params.device_id ||
- def->modem_params.network_id || def->modem_params.pin ||
- def->modem_params.sim_id || def->modem_params.sim_operator_id))
+ if ( def->modem_params.apn
+ || def->modem_params.auto_config
+ || def->modem_params.device_id
+ || def->modem_params.network_id
+ || def->modem_params.pin
+ || def->modem_params.sim_id
+ || def->modem_params.sim_operator_id)
return TRUE;
return FALSE;
@@ -115,6 +119,9 @@ type_str(const NetplanNetDefinition* def)
if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD)
return "wireguard";
return "ip-tunnel";
+ case NETPLAN_DEF_TYPE_NM:
+ /* needs to be overriden by passthrough "connection.type" setting */
+ return NULL;
// LCOV_EXCL_START
default:
g_assert_not_reached();
@@ -179,19 +186,29 @@ addr_gen_mode_str(const NetplanAddrGenMode mode)
}
static void
-write_search_domains(const NetplanNetDefinition* def, GString *s)
+write_search_domains(const NetplanNetDefinition* def, const char* group, GKeyFile *kf)
{
if (def->search_domains) {
- g_string_append(s, "dns-search=");
+ const gchar* list[def->search_domains->len];
for (unsigned i = 0; i < def->search_domains->len; ++i)
- g_string_append_printf(s, "%s;", g_array_index(def->search_domains, char*, i));
- g_string_append(s, "\n");
+ list[i] = g_array_index(def->search_domains, char*, i);
+ g_key_file_set_string_list(kf, group, "dns-search", list, def->search_domains->len);
}
}
static void
-write_routes(const NetplanNetDefinition* def, GString *s, int family)
+write_routes(const NetplanNetDefinition* def, GKeyFile *kf, int family)
{
+ const gchar* group = NULL;
+ gchar* tmp_key = NULL;
+ GString* tmp_val = NULL;
+
+ if (family == AF_INET)
+ group = "ipv4";
+ else if (family == AF_INET6)
+ group = "ipv6";
+ g_assert(group != NULL);
+
if (def->routes != NULL) {
for (unsigned i = 0, j = 1; i < def->routes->len; ++i) {
const NetplanIPRoute *cur_route = g_array_index(def->routes, NetplanIPRoute*, i);
@@ -215,11 +232,14 @@ write_routes(const NetplanNetDefinition* def, GString *s, int family)
exit(1);
}
- g_string_append_printf(s, "route%d=%s,%s",
- j, cur_route->to, cur_route->via);
+ tmp_key = g_strdup_printf("route%d", j);
+ tmp_val = g_string_new(NULL);
+ g_string_printf(tmp_val, "%s,%s", cur_route->to, cur_route->via);
if (cur_route->metric != NETPLAN_METRIC_UNSPEC)
- g_string_append_printf(s, ",%d", cur_route->metric);
- g_string_append(s, "\n");
+ g_string_append_printf(tmp_val, ",%d", cur_route->metric);
+ g_key_file_set_string(kf, group, tmp_key, tmp_val->str);
+ g_free(tmp_key);
+ g_string_free(tmp_val, TRUE);
if ( cur_route->onlink
|| cur_route->advertised_receive_window
@@ -227,22 +247,26 @@ write_routes(const NetplanNetDefinition* def, GString *s, int family)
|| cur_route->mtubytes
|| cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC
|| cur_route->from) {
- g_string_append_printf(s, "route%d_options=", j);
+ tmp_key = g_strdup_printf("route%d_options", j);
+ tmp_val = g_string_new(NULL);
if (cur_route->onlink) {
/* onlink for IPv6 addresses is only supported since nm-1.18.0. */
- g_string_append_printf(s, "onlink=true,");
+ g_string_append_printf(tmp_val, "onlink=true,");
}
if (cur_route->advertised_receive_window != NETPLAN_ADVERTISED_RECEIVE_WINDOW_UNSPEC)
- g_string_append_printf(s, "initrwnd=%u,", cur_route->advertised_receive_window);
+ g_string_append_printf(tmp_val, "initrwnd=%u,", cur_route->advertised_receive_window);
if (cur_route->congestion_window != NETPLAN_CONGESTION_WINDOW_UNSPEC)
- g_string_append_printf(s, "initcwnd=%u,", cur_route->congestion_window);
+ g_string_append_printf(tmp_val, "initcwnd=%u,", cur_route->congestion_window);
if (cur_route->mtubytes != NETPLAN_MTU_UNSPEC)
- g_string_append_printf(s, "mtu=%u,", cur_route->mtubytes);
+ g_string_append_printf(tmp_val, "mtu=%u,", cur_route->mtubytes);
if (cur_route->table != NETPLAN_ROUTE_TABLE_UNSPEC)
- g_string_append_printf(s, "table=%u,", cur_route->table);
+ g_string_append_printf(tmp_val, "table=%u,", cur_route->table);
if (cur_route->from)
- g_string_append_printf(s, "src=%s,", cur_route->from);
- s->str[s->len - 1] = '\n';
+ g_string_append_printf(tmp_val, "src=%s,", cur_route->from);
+ tmp_val->str[tmp_val->len - 1] = '\0'; //remove trailing comma
+ g_key_file_set_string(kf, group, tmp_key, tmp_val->str);
+ g_free(tmp_key);
+ g_string_free(tmp_val, TRUE);
}
j++;
}
@@ -250,100 +274,86 @@ write_routes(const NetplanNetDefinition* def, GString *s, int family)
}
static void
-write_bond_parameters(const NetplanNetDefinition* def, GString *s)
+write_bond_parameters(const NetplanNetDefinition* def, GKeyFile *kf)
{
- GString* params = NULL;
-
- params = g_string_sized_new(200);
-
+ GString* tmp_val = NULL;
if (def->bond_params.mode)
- g_string_append_printf(params, "\nmode=%s", def->bond_params.mode);
+ g_key_file_set_string(kf, "bond", "mode", def->bond_params.mode);
if (def->bond_params.lacp_rate)
- g_string_append_printf(params, "\nlacp_rate=%s", def->bond_params.lacp_rate);
+ g_key_file_set_string(kf, "bond", "lacp_rate", def->bond_params.lacp_rate);
if (def->bond_params.monitor_interval)
- g_string_append_printf(params, "\nmiimon=%s", def->bond_params.monitor_interval);
+ g_key_file_set_string(kf, "bond", "miimon", def->bond_params.monitor_interval);
if (def->bond_params.min_links)
- g_string_append_printf(params, "\nmin_links=%d", def->bond_params.min_links);
+ g_key_file_set_integer(kf, "bond", "min_links", def->bond_params.min_links);
if (def->bond_params.transmit_hash_policy)
- g_string_append_printf(params, "\nxmit_hash_policy=%s", def->bond_params.transmit_hash_policy);
+ g_key_file_set_string(kf, "bond", "xmit_hash_policy", def->bond_params.transmit_hash_policy);
if (def->bond_params.selection_logic)
- g_string_append_printf(params, "\nad_select=%s", def->bond_params.selection_logic);
+ g_key_file_set_string(kf, "bond", "ad_select", def->bond_params.selection_logic);
if (def->bond_params.all_slaves_active)
- g_string_append_printf(params, "\nall_slaves_active=%d", def->bond_params.all_slaves_active);
+ g_key_file_set_integer(kf, "bond", "all_slaves_active", def->bond_params.all_slaves_active);
if (def->bond_params.arp_interval)
- g_string_append_printf(params, "\narp_interval=%s", def->bond_params.arp_interval);
+ g_key_file_set_string(kf, "bond", "arp_interval", def->bond_params.arp_interval);
if (def->bond_params.arp_ip_targets) {
- g_string_append_printf(params, "\narp_ip_target=");
+ tmp_val = g_string_new(NULL);
for (unsigned i = 0; i < def->bond_params.arp_ip_targets->len; ++i) {
if (i > 0)
- g_string_append_printf(params, ",");
- g_string_append_printf(params, "%s", g_array_index(def->bond_params.arp_ip_targets, char*, i));
+ g_string_append_printf(tmp_val, ",");
+ g_string_append_printf(tmp_val, "%s", g_array_index(def->bond_params.arp_ip_targets, char*, i));
}
+ g_key_file_set_string(kf, "bond", "arp_ip_target", tmp_val->str);
+ g_string_free(tmp_val, TRUE);
}
if (def->bond_params.arp_validate)
- g_string_append_printf(params, "\narp_validate=%s", def->bond_params.arp_validate);
+ g_key_file_set_string(kf, "bond", "arp_validate", def->bond_params.arp_validate);
if (def->bond_params.arp_all_targets)
- g_string_append_printf(params, "\narp_all_targets=%s", def->bond_params.arp_all_targets);
+ g_key_file_set_string(kf, "bond", "arp_all_targets", def->bond_params.arp_all_targets);
if (def->bond_params.up_delay)
- g_string_append_printf(params, "\nupdelay=%s", def->bond_params.up_delay);
+ g_key_file_set_string(kf, "bond", "updelay", def->bond_params.up_delay);
if (def->bond_params.down_delay)
- g_string_append_printf(params, "\ndowndelay=%s", def->bond_params.down_delay);
+ g_key_file_set_string(kf, "bond", "downdelay", def->bond_params.down_delay);
if (def->bond_params.fail_over_mac_policy)
- g_string_append_printf(params, "\nfail_over_mac=%s", def->bond_params.fail_over_mac_policy);
+ g_key_file_set_string(kf, "bond", "fail_over_mac", def->bond_params.fail_over_mac_policy);
if (def->bond_params.gratuitous_arp) {
- g_string_append_printf(params, "\nnum_grat_arp=%d", def->bond_params.gratuitous_arp);
+ g_key_file_set_integer(kf, "bond", "num_grat_arp", def->bond_params.gratuitous_arp);
/* Work around issue in NM where unset unsolicited_na will overwrite num_grat_arp:
* https://github.com/NetworkManager/NetworkManager/commit/42b0bef33c77a0921590b2697f077e8ea7805166 */
- g_string_append_printf(params, "\nnum_unsol_na=%d", def->bond_params.gratuitous_arp);
+ g_key_file_set_integer(kf, "bond", "num_unsol_na", def->bond_params.gratuitous_arp);
}
if (def->bond_params.packets_per_slave)
- g_string_append_printf(params, "\npackets_per_slave=%d", def->bond_params.packets_per_slave);
+ g_key_file_set_integer(kf, "bond", "packets_per_slave", def->bond_params.packets_per_slave);
if (def->bond_params.primary_reselect_policy)
- g_string_append_printf(params, "\nprimary_reselect=%s", def->bond_params.primary_reselect_policy);
+ g_key_file_set_string(kf, "bond", "primary_reselect", def->bond_params.primary_reselect_policy);
if (def->bond_params.resend_igmp)
- g_string_append_printf(params, "\nresend_igmp=%d", def->bond_params.resend_igmp);
+ g_key_file_set_integer(kf, "bond", "resend_igmp", def->bond_params.resend_igmp);
if (def->bond_params.learn_interval)
- g_string_append_printf(params, "\nlp_interval=%s", def->bond_params.learn_interval);
+ g_key_file_set_string(kf, "bond", "lp_interval", def->bond_params.learn_interval);
if (def->bond_params.primary_slave)
- g_string_append_printf(params, "\nprimary=%s", def->bond_params.primary_slave);
-
- if (params->len > 0)
- g_string_append_printf(s, "\n[bond]%s\n", params->str);
-
- g_string_free(params, TRUE);
+ g_key_file_set_string(kf, "bond", "primary", def->bond_params.primary_slave);
}
static void
-write_bridge_params(const NetplanNetDefinition* def, GString *s)
+write_bridge_params(const NetplanNetDefinition* def, GKeyFile *kf)
{
- GString* params = NULL;
-
if (def->custom_bridging) {
- params = g_string_sized_new(200);
-
if (def->bridge_params.ageing_time)
- g_string_append_printf(params, "ageing-time=%s\n", def->bridge_params.ageing_time);
+ g_key_file_set_string(kf, "bridge", "ageing-time", def->bridge_params.ageing_time);
if (def->bridge_params.priority)
- g_string_append_printf(params, "priority=%u\n", def->bridge_params.priority);
+ g_key_file_set_uint64(kf, "bridge", "priority", def->bridge_params.priority);
if (def->bridge_params.forward_delay)
- g_string_append_printf(params, "forward-delay=%s\n", def->bridge_params.forward_delay);
+ g_key_file_set_string(kf, "bridge", "forward-delay", def->bridge_params.forward_delay);
if (def->bridge_params.hello_time)
- g_string_append_printf(params, "hello-time=%s\n", def->bridge_params.hello_time);
+ g_key_file_set_string(kf, "bridge", "hello-time", def->bridge_params.hello_time);
if (def->bridge_params.max_age)
- g_string_append_printf(params, "max-age=%s\n", def->bridge_params.max_age);
- g_string_append_printf(params, "stp=%s\n", def->bridge_params.stp ? "true" : "false");
-
- g_string_append_printf(s, "\n[bridge]\n%s", params->str);
-
- g_string_free(params, TRUE);
+ g_key_file_set_string(kf, "bridge", "max-age", def->bridge_params.max_age);
+ g_key_file_set_boolean(kf, "bridge", "stp", def->bridge_params.stp);
}
}
static void
-write_wireguard_params(const NetplanNetDefinition* def, GString *s)
+write_wireguard_params(const NetplanNetDefinition* def, GKeyFile *kf)
{
+ gchar* tmp_group = NULL;
g_assert(def->tunnel.private_key);
- g_string_append(s, "\n[wireguard]\n");
/* The key was already validated via validate_tunnel_grammar(), but we need
* to differentiate between base64 key VS absolute path key-file. And a base64
@@ -353,22 +363,23 @@ write_wireguard_params(const NetplanNetDefinition* def, GString *s)
g_fprintf(stderr, "%s: private key needs to be base64 encoded when using the NM backend\n", def->id);
exit(1);
} else
- g_string_append_printf(s, "private-key=%s\n", def->tunnel.private_key);
+ g_key_file_set_string(kf, "wireguard", "private-key", def->tunnel.private_key);
if (def->tunnel.port)
- g_string_append_printf(s, "listen-port=%u\n", def->tunnel.port);
+ g_key_file_set_uint64(kf, "wireguard", "listen-port", def->tunnel.port);
if (def->tunnel.fwmark)
- g_string_append_printf(s, "fwmark=%u\n", def->tunnel.fwmark);
+ g_key_file_set_uint64(kf, "wireguard", "fwmark", def->tunnel.fwmark);
for (guint i = 0; i < def->wireguard_peers->len; i++) {
NetplanWireguardPeer *peer = g_array_index (def->wireguard_peers, NetplanWireguardPeer*, i);
g_assert(peer->public_key);
- g_string_append_printf(s, "\n[wireguard-peer.%s]\n", peer->public_key);
+ tmp_group = g_strdup_printf("wireguard-peer.%s", peer->public_key);
if (peer->keepalive)
- g_string_append_printf(s, "persistent-keepalive=%d\n", peer->keepalive);
+ g_key_file_set_integer(kf, tmp_group, "persistent-keepalive", peer->keepalive);
if (peer->endpoint)
- g_string_append_printf(s, "endpoint=%s\n", peer->endpoint);
+ g_key_file_set_string(kf, tmp_group, "endpoint", peer->endpoint);
+
/* The key was already validated via validate_tunnel_grammar(), but we need
* to differentiate between base64 key VS absolute path key-file. And a base64
* string could (theoretically) start with '/', so we use is_wireguard_key()
@@ -378,112 +389,93 @@ write_wireguard_params(const NetplanNetDefinition* def, GString *s)
g_fprintf(stderr, "%s: shared key needs to be base64 encoded when using the NM backend\n", def->id);
exit(1);
} else {
- g_string_append_printf(s, "preshared-key=%s\n", peer->preshared_key);
- g_string_append(s, "preshared-key-flags=0\n");
+ g_key_file_set_value(kf, tmp_group, "preshared-key", peer->preshared_key);
+ g_key_file_set_uint64(kf, tmp_group, "preshared-key-flags", 0);
}
}
if (peer->allowed_ips && peer->allowed_ips->len > 0) {
- g_string_append(s, "allowed-ips=");
- for (guint i = 0; i < peer->allowed_ips->len; ++i) {
- if (i > 0 ) g_string_append_c(s, ';');
- g_string_append_printf(s, "%s", g_array_index(peer->allowed_ips, char*, i));
- }
- g_string_append_c(s, '\n');
+ const gchar* list[peer->allowed_ips->len];
+ for (guint j = 0; j < peer->allowed_ips->len; ++j)
+ list[j] = g_array_index(peer->allowed_ips, char*, j);
+ g_key_file_set_string_list(kf, tmp_group, "allowed-ips", list, peer->allowed_ips->len);
}
+ g_free(tmp_group);
}
}
static void
-write_tunnel_params(const NetplanNetDefinition* def, GString *s)
+write_tunnel_params(const NetplanNetDefinition* def, GKeyFile *kf)
{
- g_string_append(s, "\n[ip-tunnel]\n");
-
- g_string_append_printf(s, "mode=%d\n", def->tunnel.mode);
- g_string_append_printf(s, "local=%s\n", def->tunnel.local_ip);
- g_string_append_printf(s, "remote=%s\n", def->tunnel.remote_ip);
+ g_key_file_set_integer(kf, "ip-tunnel", "mode", def->tunnel.mode);
+ g_key_file_set_string(kf, "ip-tunnel", "local", def->tunnel.local_ip);
+ g_key_file_set_string(kf, "ip-tunnel", "remote", def->tunnel.remote_ip);
if (def->tunnel.ttl)
- g_string_append_printf(s, "ttl=%u\n", def->tunnel.ttl);
+ g_key_file_set_uint64(kf, "ip-tunnel", "ttl", def->tunnel.ttl);
if (def->tunnel.input_key)
- g_string_append_printf(s, "input-key=%s\n", def->tunnel.input_key);
+ g_key_file_set_string(kf, "ip-tunnel", "input-key", def->tunnel.input_key);
if (def->tunnel.output_key)
- g_string_append_printf(s, "output-key=%s\n", def->tunnel.output_key);
+ g_key_file_set_string(kf, "ip-tunnel", "output-key", def->tunnel.output_key);
}
static void
-write_dot1x_auth_parameters(const NetplanAuthenticationSettings* auth, GString *s)
+write_dot1x_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile *kf)
{
- if (auth->eap_method == NETPLAN_AUTH_EAP_NONE) {
+ if (auth->eap_method == NETPLAN_AUTH_EAP_NONE)
return;
- }
-
- g_string_append_printf(s, "\n[802-1x]\n");
switch (auth->eap_method) {
case NETPLAN_AUTH_EAP_NONE: break; // LCOV_EXCL_LINE
case NETPLAN_AUTH_EAP_TLS:
- g_string_append(s, "eap=tls\n");
+ g_key_file_set_string(kf, "802-1x", "eap", "tls");
break;
case NETPLAN_AUTH_EAP_PEAP:
- g_string_append(s, "eap=peap\n");
+ g_key_file_set_string(kf, "802-1x", "eap", "peap");
break;
case NETPLAN_AUTH_EAP_TTLS:
- g_string_append(s, "eap=ttls\n");
+ g_key_file_set_string(kf, "802-1x", "eap", "ttls");
break;
}
- if (auth->identity) {
- g_string_append_printf(s, "identity=%s\n", auth->identity);
- }
- if (auth->anonymous_identity) {
- g_string_append_printf(s, "anonymous-identity=%s\n", auth->anonymous_identity);
- }
- if (auth->password && auth->key_management != NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK) {
- g_string_append_printf(s, "password=%s\n", auth->password);
- }
- if (auth->ca_certificate) {
- g_string_append_printf(s, "ca-cert=%s\n", auth->ca_certificate);
- }
- if (auth->client_certificate) {
- g_string_append_printf(s, "client-cert=%s\n", auth->client_certificate);
- }
- if (auth->client_key) {
- g_string_append_printf(s, "private-key=%s\n", auth->client_key);
- }
- if (auth->client_key_password) {
- g_string_append_printf(s, "private-key-password=%s\n", auth->client_key_password);
- }
- if (auth->phase2_auth) {
- g_string_append_printf(s, "phase2-auth=%s\n", auth->phase2_auth);
- }
-
+ if (auth->identity)
+ g_key_file_set_string(kf, "802-1x", "identity", auth->identity);
+ if (auth->anonymous_identity)
+ g_key_file_set_string(kf, "802-1x", "anonymous-identity", auth->anonymous_identity);
+ if (auth->password && auth->key_management != NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK)
+ g_key_file_set_string(kf, "802-1x", "password", auth->password);
+ if (auth->ca_certificate)
+ g_key_file_set_string(kf, "802-1x", "ca-cert", auth->ca_certificate);
+ if (auth->client_certificate)
+ g_key_file_set_string(kf, "802-1x", "client-cert", auth->client_certificate);
+ if (auth->client_key)
+ g_key_file_set_string(kf, "802-1x", "private-key", auth->client_key);
+ if (auth->client_key_password)
+ g_key_file_set_string(kf, "802-1x", "private-key-password", auth->client_key_password);
+ if (auth->phase2_auth)
+ g_key_file_set_string(kf, "802-1x", "phase2-auth", auth->phase2_auth);
}
static void
-write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GString *s)
+write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile *kf)
{
- if (auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_NONE) {
+ if (auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_NONE)
return;
- }
-
- g_string_append(s, "\n[wifi-security]\n");
switch (auth->key_management) {
case NETPLAN_AUTH_KEY_MANAGEMENT_NONE: break; // LCOV_EXCL_LINE
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK:
- g_string_append(s, "key-mgmt=wpa-psk\n");
- if (auth->password) {
- g_string_append_printf(s, "psk=%s\n", auth->password);
- }
+ g_key_file_set_string(kf, "wifi-security", "key-mgmt", "wpa-psk");
+ if (auth->password)
+ g_key_file_set_string(kf, "wifi-security", "psk", auth->password);
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_EAP:
- g_string_append(s, "key-mgmt=wpa-eap\n");
+ g_key_file_set_string(kf, "wifi-security", "key-mgmt", "wpa-eap");
break;
case NETPLAN_AUTH_KEY_MANAGEMENT_8021X:
- g_string_append(s, "key-mgmt=ieee8021x\n");
+ g_key_file_set_string(kf, "wifi-security", "key-mgmt", "ieee8021x");
break;
}
- write_dot1x_auth_parameters(auth, s);
+ write_dot1x_auth_parameters(auth, kf);
}
static void
@@ -494,6 +486,52 @@ maybe_generate_uuid(NetplanNetDefinition* def)
}
/**
+ * Special handling for passthrough mode: read key-value pairs from
+ * "backend_settings.nm.passthrough" and inject them into the keyfile as-is.
+ */
+static void
+write_fallback_key_value(GQuark key_id, gpointer value, gpointer user_data)
+{
+ GKeyFile *kf = user_data;
+ gchar* val = value;
+ /* Group name may contain dots, but key name may not.
+ * The "tc" group is a special case, where it is the other way around, e.g.:
+ * tc->qdisc.root
+ * tc->tfilter.ffff: */
+ const gchar* key = g_quark_to_string(key_id);
+ gchar **group_key = g_strsplit(key, ".", -1);
+ guint len = g_strv_length(group_key);
+ g_autofree gchar* old_key = NULL;
+ gboolean has_key = FALSE;
+ g_autofree gchar* k = NULL;
+ g_autofree gchar* group = NULL;
+ if (!g_strcmp0(group_key[0], "tc") && len > 2) {
+ k = g_strconcat(group_key[1], ".", group_key[2], NULL);
+ group = g_strdup(group_key[0]);
+ } else {
+ k = group_key[len-1];
+ group_key[len-1] = NULL; //remove key from array
+ group = g_strjoinv(".", group_key); //re-combine group parts
+ }
+
+ has_key = g_key_file_has_key(kf, group, k, NULL);
+ old_key = g_key_file_get_string(kf, group, k, NULL);
+ g_key_file_set_string(kf, group, k, val);
+ /* delete the dummy key, if this was just an empty group */
+ if (!g_strcmp0(k, NETPLAN_NM_EMPTY_GROUP))
+ g_key_file_remove_key(kf, group, k, NULL);
+ else if (!has_key) {
+ g_debug("NetworkManager: passing through fallback key: %s.%s=%s", group, k, val);
+ g_key_file_set_comment(kf, group, k, "Netplan: passthrough setting", NULL);
+ } else if (!!g_strcmp0(val, old_key)) {
+ g_debug("NetworkManager: fallback override: %s.%s=%s", group, k, val);
+ g_key_file_set_comment(kf, group, k, "Netplan: passthrough override", NULL);
+ }
+
+ g_strfreev(group_key);
+}
+
+/**
* Generate NetworkManager configuration in @rootdir/run/NetworkManager/ for a
* particular NetplanNetDefinition and NetplanWifiAccessPoint, as NM requires a separate
* connection file for each SSID.
@@ -506,8 +544,13 @@ maybe_generate_uuid(NetplanNetDefinition* def)
static void
write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const NetplanWifiAccessPoint* ap)
{
- GString *s = NULL;
- g_autofree char* conf_path = NULL;
+ g_autoptr(GKeyFile) kf = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar* conf_path = NULL;
+ g_autofree gchar* full_path = NULL;
+ g_autofree gchar* nd_nm_id = NULL;
+ const gchar* nm_type = NULL;
+ gchar* tmp_key = NULL;
mode_t orig_umask;
char uuidstr[37];
const char *match_interface_name = NULL;
@@ -522,19 +565,35 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const
return;
}
- s = g_string_new(NULL);
- g_string_append_printf(s, "[connection]\nid=netplan-%s", def->id);
- if (ap)
- g_string_append_printf(s, "-%s", ap->ssid);
- g_string_append_printf(s, "\ntype=%s\n", type_str(def));
+ kf = g_key_file_new();
+ if (ap && ap->backend_settings.nm.name)
+ g_key_file_set_string(kf, "connection", "id", ap->backend_settings.nm.name);
+ else if (def->backend_settings.nm.name)
+ g_key_file_set_string(kf, "connection", "id", def->backend_settings.nm.name);
+ else {
+ /* Auto-generate a name for the connection profile, if not specified */
+ if (ap)
+ nd_nm_id = g_strdup_printf("netplan-%s-%s", def->id, ap->ssid);
+ else
+ nd_nm_id = g_strdup_printf("netplan-%s", def->id);
+ g_key_file_set_string(kf, "connection", "id", nd_nm_id);
+ }
+
+ nm_type = type_str(def);
+ if (nm_type)
+ g_key_file_set_string(kf, "connection", "type", nm_type);
+ if (ap && ap->backend_settings.nm.uuid)
+ g_key_file_set_string(kf, "connection", "uuid", ap->backend_settings.nm.uuid);
+ else if (def->backend_settings.nm.uuid)
+ g_key_file_set_string(kf, "connection", "uuid", def->backend_settings.nm.uuid);
/* VLAN devices refer to us as their parent; if our ID is not a name but we
* have matches, parent= must be the connection UUID, so put it into the
* connection */
if (def->has_vlans && def->has_match) {
maybe_generate_uuid(def);
uuid_unparse(def->uuid, uuidstr);
- g_string_append_printf(s, "uuid=%s\n", uuidstr);
+ g_key_file_set_string(kf, "connection", "uuid", uuidstr);
}
if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) {
@@ -542,69 +601,70 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const
* supported, MAC matching is done below (different keyfile section),
* so only match names here */
if (def->set_name)
- g_string_append_printf(s, "interface-name=%s\n", def->set_name);
+ g_key_file_set_string(kf, "connection", "interface-name", def->set_name);
else if (!def->has_match)
- g_string_append_printf(s, "interface-name=%s\n", def->id);
+ g_key_file_set_string(kf, "connection", "interface-name", def->id);
else if (def->match.original_name) {
if (strpbrk(def->match.original_name, "*[]?"))
match_interface_name = def->match.original_name;
else
- g_string_append_printf(s, "interface-name=%s\n", def->match.original_name);
+ g_key_file_set_string(kf, "connection", "interface-name", def->match.original_name);
}
/* else matches on something other than the name, do not restrict interface-name */
} else {
/* virtual (created) devices set a name */
- g_string_append_printf(s, "interface-name=%s\n", def->id);
+ if (strlen(def->id) > 15)
+ g_debug("interface-name longer than 15 characters is not supported");
+ else
+ g_key_file_set_string(kf, "connection", "interface-name", def->id);
if (def->type == NETPLAN_DEF_TYPE_BRIDGE)
- write_bridge_params(def, s);
+ write_bridge_params(def, kf);
}
if (def->type == NETPLAN_DEF_TYPE_MODEM) {
- if (modem_is_gsm(def))
- g_string_append_printf(s, "\n[gsm]\n");
- else
- g_string_append_printf(s, "\n[cdma]\n");
+ const char* modem_type = modem_is_gsm(def) ? "gsm" : "cdma";
/* Use NetworkManager's auto configuration feature if no APN, username, or password is specified */
if (def->modem_params.auto_config || (!def->modem_params.apn &&
!def->modem_params.username && !def->modem_params.password)) {
- g_string_append_printf(s, "auto-config=true\n");
+ g_key_file_set_boolean(kf, modem_type, "auto-config", TRUE);
} else {
if (def->modem_params.apn)
- g_string_append_printf(s, "apn=%s\n", def->modem_params.apn);
+ g_key_file_set_string(kf, modem_type, "apn", def->modem_params.apn);
if (def->modem_params.password)
- g_string_append_printf(s, "password=%s\n", def->modem_params.password);
+ g_key_file_set_string(kf, modem_type, "password", def->modem_params.password);
if (def->modem_params.username)
- g_string_append_printf(s, "username=%s\n", def->modem_params.username);
+ g_key_file_set_string(kf, modem_type, "username", def->modem_params.username);
}
if (def->modem_params.device_id)
- g_string_append_printf(s, "device-id=%s\n", def->modem_params.device_id);
+ g_key_file_set_string(kf, modem_type, "device-id", def->modem_params.device_id);
if (def->mtubytes)
- g_string_append_printf(s, "mtu=%u\n", def->mtubytes);
+ g_key_file_set_uint64(kf, modem_type, "mtu", def->mtubytes);
if (def->modem_params.network_id)
- g_string_append_printf(s, "network-id=%s\n", def->modem_params.network_id);
+ g_key_file_set_string(kf, modem_type, "network-id", def->modem_params.network_id);
if (def->modem_params.number)
- g_string_append_printf(s, "number=%s\n", def->modem_params.number);
+ g_key_file_set_string(kf, modem_type, "number", def->modem_params.number);
if (def->modem_params.pin)
- g_string_append_printf(s, "pin=%s\n", def->modem_params.pin);
+ g_key_file_set_string(kf, modem_type, "pin", def->modem_params.pin);
if (def->modem_params.sim_id)
- g_string_append_printf(s, "sim-id=%s\n", def->modem_params.sim_id);
+ g_key_file_set_string(kf, modem_type, "sim-id", def->modem_params.sim_id);
if (def->modem_params.sim_operator_id)
- g_string_append_printf(s, "sim-operator-id=%s\n", def->modem_params.sim_operator_id);
+ g_key_file_set_string(kf, modem_type, "sim-operator-id", def->modem_params.sim_operator_id);
}
if (def->bridge) {
- g_string_append_printf(s, "slave-type=bridge\nmaster=%s\n", def->bridge);
+ g_key_file_set_string(kf, "connection", "slave-type", "bridge");
+ g_key_file_set_string(kf, "connection", "master", def->bridge);
- if (def->bridge_params.path_cost || def->bridge_params.port_priority)
- g_string_append_printf(s, "\n[bridge-port]\n");
if (def->bridge_params.path_cost)
- g_string_append_printf(s, "path-cost=%u\n", def->bridge_params.path_cost);
+ g_key_file_set_uint64(kf, "bridge-port", "path-cost", def->bridge_params.path_cost);
if (def->bridge_params.port_priority)
- g_string_append_printf(s, "priority=%u\n", def->bridge_params.port_priority);
+ g_key_file_set_uint64(kf, "bridge-port", "priority", def->bridge_params.port_priority);
+ }
+ if (def->bond) {
+ g_key_file_set_string(kf, "connection", "slave-type", "bond");
+ g_key_file_set_string(kf, "connection", "master", def->bond);
}
- if (def->bond)
- g_string_append_printf(s, "slave-type=bond\nmaster=%s\n", def->bond);
if (def->ipv6_mtubytes) {
g_fprintf(stderr, "ERROR: %s: NetworkManager definitions do not support ipv6-mtu\n", def->id);
@@ -612,184 +672,176 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const
}
if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) {
- GString *link_str = NULL;
-
- link_str = g_string_new(NULL);
-
- g_string_append_printf(s, "\n[ethernet]\nwake-on-lan=%i\n", def->wake_on_lan ? 1 : 0);
-
- if (!def->set_name && def->match.mac) {
- g_string_append_printf(link_str, "mac-address=%s\n", def->match.mac);
- }
- if (def->set_mac) {
- g_string_append_printf(link_str, "cloned-mac-address=%s\n", def->set_mac);
- }
- if (def->mtubytes) {
- g_string_append_printf(link_str, "mtu=%u\n", def->mtubytes);
- }
- if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT)
- g_string_append_printf(link_str, "wake-on-wlan=%u\n", def->wowlan);
-
- if (link_str->len > 0) {
- switch (def->type) {
- case NETPLAN_DEF_TYPE_WIFI:
- g_string_append_printf(s, "\n[802-11-wireless]\n%s", link_str->str); break;
- case NETPLAN_DEF_TYPE_MODEM:
- /* Avoid adding an [ethernet] section into the [gsm/cdma] description. */
- break;
- default:
- g_string_append_printf(s, "\n[802-3-ethernet]\n%s", link_str->str); break;
- }
- }
-
- g_string_free(link_str, TRUE);
- } else {
- GString *link_str = NULL;
-
- link_str = g_string_new(NULL);
+ if (def->type == NETPLAN_DEF_TYPE_ETHERNET)
+ g_key_file_set_integer(kf, "ethernet", "wake-on-lan", def->wake_on_lan ? 1 : 0);
- if (def->set_mac) {
- g_string_append_printf(link_str, "cloned-mac-address=%s\n", def->set_mac);
- }
- if (def->mtubytes) {
- g_string_append_printf(link_str, "mtu=%u\n", def->mtubytes);
+ const char* con_type = NULL;
+ switch (def->type) {
+ case NETPLAN_DEF_TYPE_WIFI:
+ con_type = "wifi";
+ case NETPLAN_DEF_TYPE_MODEM:
+ /* Avoid adding an [ethernet] section into the [gsm/cdma] description. */
+ break;
+ default:
+ con_type = "ethernet";
}
- if (link_str->len > 0) {
- g_string_append_printf(s, "\n[802-3-ethernet]\n%s", link_str->str);
+ if (con_type) {
+ if (!def->set_name && def->match.mac)
+ g_key_file_set_string(kf, con_type, "mac-address", def->match.mac);
+ if (def->set_mac)
+ g_key_file_set_string(kf, con_type, "cloned-mac-address", def->set_mac);
+ if (def->mtubytes)
+ g_key_file_set_uint64(kf, con_type, "mtu", def->mtubytes);
+ if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT)
+ g_key_file_set_uint64(kf, con_type, "wake-on-wlan", def->wowlan);
}
-
- g_string_free(link_str, TRUE);
+ } else {
+ if (def->set_mac)
+ g_key_file_set_string(kf, "ethernet", "cloned-mac-address", def->set_mac);
+ if (def->mtubytes)
+ g_key_file_set_uint64(kf, "ethernet", "mtu", def->mtubytes);
}
if (def->type == NETPLAN_DEF_TYPE_VLAN) {
g_assert(def->vlan_id < G_MAXUINT);
g_assert(def->vlan_link != NULL);
- g_string_append_printf(s, "\n[vlan]\nid=%u\nparent=", def->vlan_id);
+ g_key_file_set_uint64(kf, "vlan", "id", def->vlan_id);
if (def->vlan_link->has_match) {
/* we need to refer to the parent's UUID as we don't have an
* interface name with match: */
maybe_generate_uuid(def->vlan_link);
uuid_unparse(def->vlan_link->uuid, uuidstr);
- g_string_append_printf(s, "%s\n", uuidstr);
+ g_key_file_set_string(kf, "vlan", "parent", uuidstr);
} else {
/* if we have an interface name, use that as parent */
- g_string_append_printf(s, "%s\n", def->vlan_link->id);
+ g_key_file_set_string(kf, "vlan", "parent", def->vlan_link->id);
}
}
if (def->type == NETPLAN_DEF_TYPE_BOND)
- write_bond_parameters(def, s);
+ write_bond_parameters(def, kf);
if (def->type == NETPLAN_DEF_TYPE_TUNNEL) {
if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD)
- write_wireguard_params(def, s);
+ write_wireguard_params(def, kf);
else
- write_tunnel_params(def, s);
+ write_tunnel_params(def, kf);
}
if (match_interface_name) {
- g_string_append(s, "\n[match]\n");
- g_string_append_printf(s, "interface-name=%s;\n", match_interface_name);
+ const gchar* list[1] = {match_interface_name};
+ g_key_file_set_string_list(kf, "match", "interface-name", list, 1);
}
- g_string_append(s, "\n[ipv4]\n");
-
if (ap && ap->mode == NETPLAN_WIFI_MODE_AP)
- g_string_append(s, "method=shared\n");
+ g_key_file_set_string(kf, "ipv4", "method", "shared");
else if (def->dhcp4)
- g_string_append(s, "method=auto\n");
+ g_key_file_set_string(kf, "ipv4", "method", "auto");
else if (def->ip4_addresses)
/* This requires adding at least one address (done below) */
- g_string_append(s, "method=manual\n");
+ g_key_file_set_string(kf, "ipv4", "method", "manual");
else if (def->type == NETPLAN_DEF_TYPE_TUNNEL)
/* sit tunnels will not start in link-local apparently */
- g_string_append(s, "method=disabled\n");
+ g_key_file_set_string(kf, "ipv4", "method", "disabled");
else
/* Without any address, this is the only available mode */
- g_string_append(s, "method=link-local\n");
+ g_key_file_set_string(kf, "ipv4", "method", "link-local");
- if (def->ip4_addresses)
- for (unsigned i = 0; i < def->ip4_addresses->len; ++i)
- g_string_append_printf(s, "address%i=%s\n", i+1, g_array_index(def->ip4_addresses, char*, i));
+ if (def->ip4_addresses) {
+ for (unsigned i = 0; i < def->ip4_addresses->len; ++i) {
+ tmp_key = g_strdup_printf("address%i", i+1);
+ g_key_file_set_string(kf, "ipv4", tmp_key, g_array_index(def->ip4_addresses, char*, i));
+ g_free(tmp_key);
+ }
+ }
if (def->gateway4)
- g_string_append_printf(s, "gateway=%s\n", def->gateway4);
+ g_key_file_set_string(kf, "ipv4", "gateway", def->gateway4);
if (def->ip4_nameservers) {
- g_string_append(s, "dns=");
+ const gchar* list[def->ip4_nameservers->len];
for (unsigned i = 0; i < def->ip4_nameservers->len; ++i)
- g_string_append_printf(s, "%s;", g_array_index(def->ip4_nameservers, char*, i));
- g_string_append(s, "\n");
+ list[i] = g_array_index(def->ip4_nameservers, char*, i);
+ g_key_file_set_string_list(kf, "ipv4", "dns", list, def->ip4_nameservers->len);
}
/* We can only write search domains and routes if we have an address */
if (def->ip4_addresses || def->dhcp4) {
- write_search_domains(def, s);
- write_routes(def, s, AF_INET);
+ write_search_domains(def, "ipv4", kf);
+ write_routes(def, kf, AF_INET);
}
if (!def->dhcp4_overrides.use_routes) {
- g_string_append(s, "ignore-auto-routes=true\n");
- g_string_append(s, "never-default=true\n");
+ g_key_file_set_boolean(kf, "ipv4", "ignore-auto-routes", TRUE);
+ g_key_file_set_boolean(kf, "ipv4", "never-default", TRUE);
}
if (def->dhcp4 && def->dhcp4_overrides.metric != NETPLAN_METRIC_UNSPEC)
- g_string_append_printf(s, "route-metric=%u\n", def->dhcp4_overrides.metric);
+ g_key_file_set_uint64(kf, "ipv4", "route-metric", def->dhcp4_overrides.metric);
if (def->dhcp6 || def->ip6_addresses || def->gateway6 || def->ip6_nameservers || def->ip6_addr_gen_mode) {
- g_string_append(s, "\n[ipv6]\n");
- g_string_append(s, def->dhcp6 ? "method=auto\n" : "method=manual\n");
- if (def->ip6_addresses)
- for (unsigned i = 0; i < def->ip6_addresses->len; ++i)
- g_string_append_printf(s, "address%i=%s\n", i+1, g_array_index(def->ip6_addresses, char*, i));
+ g_key_file_set_string(kf, "ipv6", "method", def->dhcp6 ? "auto" : "manual");
+
+ if (def->ip6_addresses) {
+ for (unsigned i = 0; i < def->ip6_addresses->len; ++i) {
+ tmp_key = g_strdup_printf("address%i", i+1);
+ g_key_file_set_string(kf, "ipv6", tmp_key, g_array_index(def->ip6_addresses, char*, i));
+ g_free(tmp_key);
+ }
+ }
if (def->ip6_addr_gen_token) {
/* Token implies EUI-64, i.e mode=0 */
- g_string_append(s, "addr-gen-mode=0\n");
- g_string_append_printf(s, "token=%s\n", def->ip6_addr_gen_token);
- } else if (def->ip6_addr_gen_mode) {
- g_string_append_printf(s, "addr-gen-mode=%s\n", addr_gen_mode_str(def->ip6_addr_gen_mode));
- }
+ g_key_file_set_integer(kf, "ipv6", "addr-gen-mode", 0);
+ g_key_file_set_string(kf, "ipv6", "token", def->ip6_addr_gen_token);
+ } else if (def->ip6_addr_gen_mode)
+ g_key_file_set_string(kf, "ipv6", "addr-gen-mode", addr_gen_mode_str(def->ip6_addr_gen_mode));
if (def->ip6_privacy)
- g_string_append(s, "ip6-privacy=2\n");
+ g_key_file_set_integer(kf, "ipv6", "ip6-privacy", 2);
if (def->gateway6)
- g_string_append_printf(s, "gateway=%s\n", def->gateway6);
+ g_key_file_set_string(kf, "ipv6", "gateway", def->gateway6);
if (def->ip6_nameservers) {
- g_string_append(s, "dns=");
+ const gchar* list[def->ip6_nameservers->len];
for (unsigned i = 0; i < def->ip6_nameservers->len; ++i)
- g_string_append_printf(s, "%s;", g_array_index(def->ip6_nameservers, char*, i));
- g_string_append(s, "\n");
+ list[i] = g_array_index(def->ip6_nameservers, char*, i);
+ g_key_file_set_string_list(kf, "ipv6", "dns", list, def->ip6_nameservers->len);
}
/* nm-settings(5) specifies search-domain for both [ipv4] and [ipv6] --
* We need to specify it here for the IPv6-only case - see LP: #1786726 */
- write_search_domains(def, s);
+ write_search_domains(def, "ipv6", kf);
/* We can only write valid routes if there is a DHCPv6 or static IPv6 address */
- write_routes(def, s, AF_INET6);
+ write_routes(def, kf, AF_INET6);
if (!def->dhcp6_overrides.use_routes) {
- g_string_append(s, "ignore-auto-routes=true\n");
- g_string_append(s, "never-default=true\n");
+ g_key_file_set_boolean(kf, "ipv6", "ignore-auto-routes", TRUE);
+ g_key_file_set_boolean(kf, "ipv6", "never-default", TRUE);
}
if (def->dhcp6_overrides.metric != NETPLAN_METRIC_UNSPEC)
- g_string_append_printf(s, "route-metric=%u\n", def->dhcp6_overrides.metric);
+ g_key_file_set_uint64(kf, "ipv6", "route-metric", def->dhcp6_overrides.metric);
}
- else {
- g_string_append(s, "\n[ipv6]\nmethod=ignore\n");
+ else
+ g_key_file_set_string(kf, "ipv6", "method", "ignore");
+
+ if (def->backend_settings.nm.passthrough) {
+ g_debug("NetworkManager: using keyfile passthrough mode");
+ /* Write all key-value pairs from the hashtable into the keyfile,
+ * potentially overriding existing values, if not fully supported. */
+ g_datalist_foreach(&def->backend_settings.nm.passthrough, write_fallback_key_value, kf);
}
if (ap) {
g_autofree char* escaped_ssid = g_uri_escape_string(ap->ssid, NULL, TRUE);
conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, "-", escaped_ssid, ".nmconnection", NULL);
- g_string_append_printf(s, "\n[wifi]\nssid=%s\nmode=%s\n", ap->ssid, wifi_mode_str(ap->mode));
- if (ap->bssid) {
- g_string_append_printf(s, "bssid=%s\n", ap->bssid);
- }
- if (ap->hidden) {
- g_string_append(s, "hidden=true\n");
- }
+ g_key_file_set_string(kf, "wifi", "ssid", ap->ssid);
+ if (ap->mode < NETPLAN_WIFI_MODE_OTHER)
+ g_key_file_set_string(kf, "wifi", "mode", wifi_mode_str(ap->mode));
+ if (ap->bssid)
+ g_key_file_set_string(kf, "wifi", "bssid", ap->bssid);
+ if (ap->hidden)
+ g_key_file_set_boolean(kf, "wifi", "hidden", TRUE);
if (ap->band == NETPLAN_WIFI_BAND_5 || ap->band == NETPLAN_WIFI_BAND_24) {
- g_string_append_printf(s, "band=%s\n", wifi_band_str(ap->band));
+ g_key_file_set_string(kf, "wifi", "band", wifi_band_str(ap->band));
/* Channel is only unambiguous, if band is set. */
if (ap->channel) {
/* Validate WiFi channel */
@@ -797,22 +849,38 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const
wifi_get_freq5(ap->channel);
else
wifi_get_freq24(ap->channel);
- g_string_append_printf(s, "channel=%u\n", ap->channel);
+ g_key_file_set_uint64(kf, "wifi", "channel", ap->channel);
}
}
if (ap->has_auth) {
- write_wifi_auth_parameters(&ap->auth, s);
+ write_wifi_auth_parameters(&ap->auth, kf);
+ }
+ if (ap->backend_settings.nm.passthrough) {
+ g_debug("NetworkManager: using AP keyfile passthrough mode");
+ /* Write all key-value pairs from the hashtable into the keyfile,
+ * potentially overriding existing values, if not fully supported.
+ * AP passthrough values have higher priority than ND passthrough,
+ * because they are more specific and bound to the current SSID's
+ * NM connection profile. */
+ g_datalist_foreach((GData**)&ap->backend_settings.nm.passthrough, write_fallback_key_value, kf);
}
} else {
conf_path = g_strjoin(NULL, "run/NetworkManager/system-connections/netplan-", def->id, ".nmconnection", NULL);
if (def->has_auth) {
- write_dot1x_auth_parameters(&def->auth, s);
+ write_dot1x_auth_parameters(&def->auth, kf);
}
}
/* NM connection files might contain secrets, and NM insists on tight permissions */
+ full_path = g_strjoin(G_DIR_SEPARATOR_S, rootdir ?: "", conf_path, NULL);
orig_umask = umask(077);
- g_string_free_to_file(s, rootdir, conf_path, NULL);
+ safe_mkdir_p_dir(full_path);
+ if (!g_key_file_save_to_file(kf, full_path, &error)) {
+ // LCOV_EXCL_START
+ g_fprintf(stderr, "ERROR: cannot create file %s: %s\n", full_path, error->message);
+ exit(1);
+ // LCOV_EXCL_STO
+ }
umask(orig_umask);
}
@@ -840,7 +908,6 @@ write_nm_conf(NetplanNetDefinition* def, const char* rootdir)
exit(1);
}
- /* for wifi we need to create a separate connection file for every SSID */
if (def->type == NETPLAN_DEF_TYPE_WIFI) {
GHashTableIter iter;
gpointer key;
@@ -879,7 +946,7 @@ write_nm_conf_finish(const char* rootdir)
GString *s = NULL;
gsize len;
- if (g_hash_table_size(netdefs) == 0)
+ if (!netdefs || g_hash_table_size(netdefs) == 0)
return;
/* Set all devices not managed by us to unmanaged, so that NM does not
diff --git a/src/parse-nm.c b/src/parse-nm.c
new file mode 100644
index 0000000..ea2a565
--- /dev/null
+++ b/src/parse-nm.c
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2021 Canonical, Ltd.
+ * Author: Lukas Märdian <slyon@ubuntu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+#include <yaml.h>
+
+#include "netplan.h"
+#include "parse-nm.h"
+#include "parse.h"
+#include "util.h"
+
+/**
+ * NetworkManager writes the alias for '802-3-ethernet' (ethernet),
+ * '802-11-wireless' (wifi) and '802-11-wireless-security' (wifi-security)
+ * by default, so we only need to check for those. See:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=696940
+ * https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/commit/c36200a225aefb2a3919618e75682646899b82c0
+ */
+static const NetplanDefType
+type_from_str(const char* type_str)
+{
+ if (!g_strcmp0(type_str, "ethernet") || !g_strcmp0(type_str, "802-3-ethernet"))
+ return NETPLAN_DEF_TYPE_ETHERNET;
+ else if (!g_strcmp0(type_str, "wifi") || !g_strcmp0(type_str, "802-11-wireless"))
+ return NETPLAN_DEF_TYPE_WIFI;
+ else if (!g_strcmp0(type_str, "gsm") || !g_strcmp0(type_str, "cdma"))
+ return NETPLAN_DEF_TYPE_MODEM;
+ else if (!g_strcmp0(type_str, "bridge"))
+ return NETPLAN_DEF_TYPE_BRIDGE;
+ else if (!g_strcmp0(type_str, "bond"))
+ return NETPLAN_DEF_TYPE_BOND;
+ else if (!g_strcmp0(type_str, "vlan"))
+ return NETPLAN_DEF_TYPE_VLAN;
+ else if (!g_strcmp0(type_str, "ip-tunnel") || !g_strcmp0(type_str, "wireguard"))
+ return NETPLAN_DEF_TYPE_TUNNEL;
+ /* Unsupported type, needs to be specified via passthrough */
+ return NETPLAN_DEF_TYPE_NM;
+}
+
+static const NetplanWifiMode
+ap_type_from_str(const char* type_str)
+{
+ if (!g_strcmp0(type_str, "infrastructure"))
+ return NETPLAN_WIFI_MODE_INFRASTRUCTURE;
+ else if (!g_strcmp0(type_str, "ap"))
+ return NETPLAN_WIFI_MODE_AP;
+ else if (!g_strcmp0(type_str, "adhoc"))
+ return NETPLAN_WIFI_MODE_ADHOC;
+ /* Unsupported mode, like "mesh" */
+ return NETPLAN_WIFI_MODE_OTHER;
+}
+
+static gboolean
+_kf_clear_key(GKeyFile* kf, const gchar* group, const gchar* key)
+{
+ gsize len = 1;
+ gboolean ret = FALSE;
+ ret = g_key_file_remove_key(kf, group, key, NULL);
+ g_strfreev(g_key_file_get_keys(kf, group, &len, NULL));
+ /* clear group if this was the last key */
+ if (len == 0)
+ ret &= g_key_file_remove_group(kf, group, NULL);
+ return ret;
+}
+
+/* Read the key-value pairs from the keyfile and pass them through to a map */
+static void
+read_passthrough(GKeyFile* kf, GData** list)
+{
+ gchar **groups = NULL;
+ gchar **keys = NULL;
+ gchar *group_key = NULL;
+ gchar *value = NULL;
+ gsize klen = 0;
+ gsize glen = 0;
+
+ if (!*list)
+ g_datalist_init(list);
+ groups = g_key_file_get_groups(kf, &glen);
+ if (groups) {
+ for (unsigned i = 0; i < glen; ++i) {
+ klen = 0;
+ keys = g_key_file_get_keys(kf, groups[i], &klen, NULL);
+ if (klen == 0) {
+ /* empty group */
+ g_datalist_set_data_full(list, g_strconcat(groups[i], ".", NETPLAN_NM_EMPTY_GROUP, NULL), g_strdup(""), g_free);
+ continue;
+ }
+ for (unsigned j = 0; j < klen; ++j) {
+ value = g_key_file_get_string(kf, groups[i], keys[j], NULL);
+ if (!value) {
+ // LCOV_EXCL_START
+ g_warning("netplan: Keyfile: cannot read value of %s.%s", groups[i], keys[j]);
+ continue;
+ // LCOV_EXCL_STOP
+ }
+ group_key = g_strconcat(groups[i], ".", keys[j], NULL);
+ g_datalist_set_data_full(list, group_key, value, g_free);
+ /* no need to free group_key and value: they stay in the list */
+ }
+ g_strfreev(keys);
+ }
+ g_strfreev(groups);
+ }
+}
+
+/**
+ * Parse keyfile into a NetplanNetDefinition struct
+ * @filename: full path to the NetworkManager keyfile
+ */
+gboolean
+netplan_parse_keyfile(const char* filename, GError** error)
+{
+ g_autofree gchar *nd_id = NULL;
+ g_autofree gchar *uuid = NULL;
+ g_autofree gchar *type = NULL;
+ g_autofree gchar* wifi_mode = NULL;
+ g_autofree gchar* ssid = NULL;
+ g_autofree gchar* netdef_id = NULL;
+ NetplanNetDefinition* nd = NULL;
+ NetplanWifiAccessPoint* ap = NULL;
+ g_autoptr(GKeyFile) kf = g_key_file_new();
+ NetplanDefType nd_type = NETPLAN_DEF_TYPE_NONE;
+ if (!g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, error)) {
+ g_warning("netplan: cannot load keyfile");
+ return FALSE;
+ }
+
+ ssid = g_key_file_get_string(kf, "wifi", "ssid", NULL);
+ if (!ssid)
+ ssid = g_key_file_get_string(kf, "802-11-wireless", "ssid", NULL);
+
+ netdef_id = netplan_get_id_from_nm_filename(filename, ssid);
+ uuid = g_key_file_get_string(kf, "connection", "uuid", NULL);
+ if (!uuid) {
+ g_warning("netplan: Keyfile: cannot find connection.uuid");
+ return FALSE;
+ }
+
+ type = g_key_file_get_string(kf, "connection", "type", NULL);
+ if (!type) {
+ g_warning("netplan: Keyfile: cannot find connection.type");
+ return FALSE;
+ }
+ nd_type = type_from_str(type);
+
+ /* Use previously existing netdef IDs, if available, to override connections
+ * Else: generate a "NM-<UUID>" ID */
+ if (netdef_id)
+ nd_id = g_strdup(netdef_id);
+ else
+ nd_id = g_strconcat("NM-", uuid, NULL);
+ nd = netplan_netdef_new(nd_id, nd_type, NETPLAN_BACKEND_NM);
+
+ /* Handle uuid & NM name/id */
+ nd->backend_settings.nm.uuid = g_strdup(uuid);
+ _kf_clear_key(kf, "connection", "uuid");
+ nd->backend_settings.nm.name = g_key_file_get_string(kf, "connection", "id", NULL);
+ if (nd->backend_settings.nm.name)
+ _kf_clear_key(kf, "connection", "id");
+
+ if (nd_type == NETPLAN_DEF_TYPE_NM)
+ goto only_passthrough; //do not try to handle any keys for connections types unknown to netplan
+
+ /* remove supported values from passthrough, which have been handled */
+ if ( nd_type == NETPLAN_DEF_TYPE_ETHERNET
+ || nd_type == NETPLAN_DEF_TYPE_WIFI
+ || nd_type == NETPLAN_DEF_TYPE_MODEM
+ || nd_type == NETPLAN_DEF_TYPE_BRIDGE
+ || nd_type == NETPLAN_DEF_TYPE_BOND
+ || nd_type == NETPLAN_DEF_TYPE_VLAN)
+ _kf_clear_key(kf, "connection", "type");
+
+ /* Handle match: Netplan usually defines a connection per interface, while
+ * NM connection profiles are usually applied to any interface of matching
+ * type (like wifi/ethernet/...). */
+ if (nd->type < NETPLAN_DEF_TYPE_VIRTUAL) {
+ nd->match.original_name = g_key_file_get_string(kf, "connection", "interface-name", NULL);
+ if (nd->match.original_name)
+ _kf_clear_key(kf, "connection", "interface-name");
+ /* Set match, even if it is empty, so the NM renderer will not force
+ * the netdef ID as interface-name */
+ nd->has_match = TRUE;
+ }
+
+ /* Modem parameters
+ * NM differentiates between GSM and CDMA connections, while netplan
+ * combines them as "modems". We need to parse a basic set of parameters
+ * to enable the generator (in nm.c) to detect GSM vs CDMA connections,
+ * using its modem_is_gsm() util. */
+ nd->modem_params.auto_config = g_key_file_get_boolean(kf, "gsm", "auto-config", NULL);
+ _kf_clear_key(kf, "gsm", "auto-config");
+ nd->modem_params.apn = g_key_file_get_string(kf, "gsm", "apn", NULL);
+ if (nd->modem_params.apn)
+ _kf_clear_key(kf, "gsm", "apn");
+ nd->modem_params.device_id = g_key_file_get_string(kf, "gsm", "device-id", NULL);
+ if (nd->modem_params.device_id)
+ _kf_clear_key(kf, "gsm", "device-id");
+ nd->modem_params.network_id = g_key_file_get_string(kf, "gsm", "network-id", NULL);
+ if (nd->modem_params.network_id)
+ _kf_clear_key(kf, "gsm", "network-id");
+ nd->modem_params.pin = g_key_file_get_string(kf, "gsm", "pin", NULL);
+ if (nd->modem_params.pin)
+ _kf_clear_key(kf, "gsm", "pin");
+ nd->modem_params.sim_id = g_key_file_get_string(kf, "gsm", "sim-id", NULL);
+ if (nd->modem_params.sim_id)
+ _kf_clear_key(kf, "gsm", "sim-id");
+ nd->modem_params.sim_operator_id = g_key_file_get_string(kf, "gsm", "sim-operator-id", NULL);
+ if (nd->modem_params.sim_operator_id)
+ _kf_clear_key(kf, "gsm", "sim-operator-id");
+
+ /* wake-on-lan, do not clear passthrough as we do not fully support this setting */
+ if (g_key_file_has_group(kf, "ethernet")) {
+ if (!g_key_file_has_key(kf, "ethernet", "wake-on-lan", NULL)) {
+ nd->wake_on_lan = TRUE; //NM's default is "1"
+ } else {
+ //XXX: fix delta between options in NM (0x1, 0x2, 0x4, ...) and netplan (bool)
+ nd->wake_on_lan = g_key_file_get_uint64(kf, "ethernet", "wake-on-lan", NULL) > 0;
+ }
+ }
+
+ /* Special handling for WiFi "access-points:" mapping */
+ if (nd->type == NETPLAN_DEF_TYPE_WIFI) {
+ ap = g_new0(NetplanWifiAccessPoint, 1);
+ ap->ssid = g_key_file_get_string(kf, "wifi", "ssid", NULL);
+ if (!ap->ssid) {
+ g_warning("netplan: Keyfile: cannot find SSID for WiFi connection");
+ return FALSE;
+ } else
+ _kf_clear_key(kf, "wifi", "ssid");
+
+ wifi_mode = g_key_file_get_string(kf, "wifi", "mode", NULL);
+ if (wifi_mode) {
+ ap->mode = ap_type_from_str(wifi_mode);
+ if (ap->mode != NETPLAN_WIFI_MODE_OTHER)
+ _kf_clear_key(kf, "wifi", "mode");
+ }
+
+ ap->hidden = g_key_file_get_boolean(kf, "wifi", "hidden", NULL);
+ _kf_clear_key(kf, "wifi", "hidden");
+
+ if (!nd->access_points)
+ nd->access_points = g_hash_table_new(g_str_hash, g_str_equal);
+ g_hash_table_insert(nd->access_points, ap->ssid, ap);
+
+ /* Last: handle passthrough for everything left in the keyfile
+ * Also, transfer backend_settings from netdef to AP */
+ ap->backend_settings.nm.uuid = nd->backend_settings.nm.uuid;
+ ap->backend_settings.nm.name = nd->backend_settings.nm.name;
+ /* No need to clear nm.uuid & nm.name from def->backend_settings,
+ * as we have only one AP. */
+ read_passthrough(kf, &ap->backend_settings.nm.passthrough);
+ } else {
+only_passthrough:
+ /* Last: handle passthrough for everything left in the keyfile */
+ read_passthrough(kf, &nd->backend_settings.nm.passthrough);
+ }
+
+ g_key_file_free(kf);
+ return TRUE;
+}
diff --git a/src/parse-nm.h b/src/parse-nm.h
new file mode 100644
index 0000000..53973f7
--- /dev/null
+++ b/src/parse-nm.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 Canonical, Ltd.
+ * Author: Lukas Märdian <slyon@ubuntu.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#define NETPLAN_NM_EMPTY_GROUP "_"
+
+gboolean netplan_parse_keyfile(const char* filename, GError** error);
diff --git a/src/parse.c b/src/parse.c
index 2673ec9..dcea8bc 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -28,6 +28,7 @@
#include <yaml.h>
#include "parse.h"
+#include "util.h"
#include "error.h"
#include "validation.h"
@@ -58,6 +59,9 @@ static NetplanAddressOptions* cur_addr_option;
static NetplanIPRoute* cur_route;
static NetplanIPRule* cur_ip_rule;
+/* Filename of the currently parsed YAML file */
+const char* cur_filename;
+
static NetplanBackend backend_global, backend_cur_type;
/* global OpenVSwitch settings */
@@ -211,7 +215,7 @@ initialize_ovs_settings(NetplanOVSSettings* ovs_settings)
ovs_settings->rstp = FALSE;
}
-static NetplanNetDefinition*
+NetplanNetDefinition*
netplan_netdef_new(const char* id, NetplanDefType type, NetplanBackend backend)
{
/* create new network definition */
@@ -236,6 +240,8 @@ netplan_netdef_new(const char* id, NetplanDefType type, NetplanBackend backend)
/* OpenVSwitch defaults */
initialize_ovs_settings(&cur_netdef->ovs_settings);
+ if (!netdefs)
+ netdefs = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_insert(netdefs, cur_netdef->id, cur_netdef);
netdefs_ordered = g_list_append(netdefs_ordered, cur_netdef);
return cur_netdef;
@@ -446,6 +452,34 @@ handle_generic_map(yaml_document_t* doc, yaml_node_t* node, void* entryptr, cons
return TRUE;
}
+/*
+ * Handler for setting a DataList field from a mapping node, inside a given struct
+ * @entryptr: pointer to the beginning of the to-be-modified data structure
+ * @data: offset into entryptr struct where the boolean field to write is located
+*/
+static gboolean
+handle_generic_datalist(yaml_document_t* doc, yaml_node_t* node, void* entryptr, const void* data, GError** error)
+{
+ guint offset = GPOINTER_TO_UINT(data);
+ GData** list = (GData**) ((void*) entryptr + offset);
+ if (!*list)
+ g_datalist_init(list);
+
+ for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) {
+ yaml_node_t* key, *value;
+
+ key = yaml_document_get_node(doc, entry->key);
+ value = yaml_document_get_node(doc, entry->value);
+
+ assert_type(key, YAML_SCALAR_NODE);
+ assert_type(value, YAML_SCALAR_NODE);
+
+ g_datalist_set_data_full(list, g_strdup(scalar(key)), g_strdup(scalar(value)), g_free);
+ }
+
+ return TRUE;
+}
+
/**
* Generic handler for setting a cur_netdef string field from a scalar node
* @data: offset into NetplanNetDefinition where the const char* field to write is
@@ -620,6 +654,13 @@ handle_netdef_map(yaml_document_t* doc, yaml_node_t* node, const void* data, GEr
return handle_generic_map(doc, node, cur_netdef, data, error);
}
+static gboolean
+handle_netdef_datalist(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error)
+{
+ g_assert(cur_netdef);
+ return handle_generic_datalist(doc, node, cur_netdef, data, error);
+}
+
/****************************************************
* Grammar and handlers for network config "match" entry
****************************************************/
@@ -708,6 +749,19 @@ get_default_backend_for_type(NetplanDefType type)
}
static gboolean
+handle_access_point_str(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error)
+{
+ return handle_generic_str(doc, node, cur_access_point, data, error);
+}
+
+static gboolean
+handle_access_point_datalist(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error)
+{
+ g_assert(cur_access_point);
+ return handle_generic_datalist(doc, node, cur_access_point, data, error);
+}
+
+static gboolean
handle_access_point_guint(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error)
{
return handle_generic_guint(doc, node, cur_access_point, data, error);
@@ -780,6 +834,29 @@ handle_access_point_band(yaml_document_t* doc, yaml_node_t* node, const void* _,
return TRUE;
}
+/* Keep in sync with ap_nm_backend_settings_handlers */
+static const mapping_entry_handler nm_backend_settings_handlers[] = {
+ {"name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.name)},
+ {"uuid", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.uuid)},
+ {"stable-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.stable_id)},
+ {"device", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.device)},
+ /* Fallback mode, to support all NM settings of the NetworkManager netplan backend */
+ {"passthrough", YAML_MAPPING_NODE, handle_netdef_datalist, NULL, netdef_offset(backend_settings.nm.passthrough)},
+ {NULL}
+};
+
+/* Keep in sync with nm_backend_settings_handlers */
+static const mapping_entry_handler ap_nm_backend_settings_handlers[] = {
+ {"name", YAML_SCALAR_NODE, handle_access_point_str, NULL, access_point_offset(backend_settings.nm.name)},
+ {"uuid", YAML_SCALAR_NODE, handle_access_point_str, NULL, access_point_offset(backend_settings.nm.uuid)},
+ {"stable-id", YAML_SCALAR_NODE, handle_access_point_str, NULL, access_point_offset(backend_settings.nm.stable_id)},
+ {"device", YAML_SCALAR_NODE, handle_access_point_str, NULL, access_point_offset(backend_settings.nm.device)},
+ /* Fallback mode, to support all NM settings of the NetworkManager netplan backend */
+ {"passthrough", YAML_MAPPING_NODE, handle_access_point_datalist, NULL, access_point_offset(backend_settings.nm.passthrough)},
+ {NULL}
+};
+
+
static const mapping_entry_handler wifi_access_point_handlers[] = {
{"band", YAML_SCALAR_NODE, handle_access_point_band},
{"bssid", YAML_SCALAR_NODE, handle_access_point_mac, NULL, access_point_offset(bssid)},
@@ -788,6 +865,7 @@ static const mapping_entry_handler wifi_access_point_handlers[] = {
{"mode", YAML_SCALAR_NODE, handle_access_point_mode},
{"password", YAML_SCALAR_NODE, handle_access_point_password},
{"auth", YAML_MAPPING_NODE, handle_access_point_auth},
+ {"networkmanager", YAML_MAPPING_NODE, NULL, ap_nm_backend_settings_handlers},
{NULL}
};
@@ -1933,14 +2011,6 @@ handle_wireguard_peers(yaml_document_t* doc, yaml_node_t* node, const void* _, G
* Grammar and handlers for network devices
****************************************************/
-static const mapping_entry_handler nm_backend_settings_handlers[] = {
- {"name", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.name)},
- {"uuid", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.uuid)},
- {"stable-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.stable_id)},
- {"device", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(backend_settings.nm.device)},
- {NULL}
-};
-
static gboolean
handle_ovs_bond_lacp(yaml_document_t* doc, yaml_node_t* node, const void* data, GError** error)
{
@@ -2234,6 +2304,7 @@ static const mapping_entry_handler vlan_def_handlers[] = {
static const mapping_entry_handler modem_def_handlers[] = {
COMMON_LINK_HANDLERS,
COMMON_BACKEND_HANDLERS,
+ PHYSICAL_LINK_HANDLERS,
{"apn", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.apn)},
{"auto-config", YAML_SCALAR_NODE, handle_netdef_bool, NULL, netdef_offset(modem_params.auto_config)},
{"device-id", YAML_SCALAR_NODE, handle_netdef_str, NULL, netdef_offset(modem_params.device_id)},
@@ -2326,7 +2397,7 @@ handle_network_ovs_settings_global_ports(yaml_document_t* doc, yaml_node_t* node
assert_type(peer, YAML_SCALAR_NODE);
/* Create port 1 netdef */
- component = g_hash_table_lookup(netdefs, scalar(port));
+ component = netdefs ? g_hash_table_lookup(netdefs, scalar(port)) : NULL;
if (!component) {
component = netplan_netdef_new(scalar(port), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS);
if (g_hash_table_remove(missing_id, scalar(port)))
@@ -2340,7 +2411,7 @@ handle_network_ovs_settings_global_ports(yaml_document_t* doc, yaml_node_t* node
/* Create port 2 (peer) netdef */
component = NULL;
- component = g_hash_table_lookup(netdefs, scalar(peer));
+ component = netdefs ? g_hash_table_lookup(netdefs, scalar(peer)) : NULL;
if (!component) {
component = netplan_netdef_new(scalar(peer), NETPLAN_DEF_TYPE_PORT, NETPLAN_BACKEND_OVS);
if (g_hash_table_remove(missing_id, scalar(peer)))
@@ -2390,14 +2461,16 @@ handle_network_type(yaml_document_t* doc, yaml_node_t* node, const void* data, G
if(g_hash_table_remove(missing_id, scalar(key)))
missing_ids_found++;
- cur_netdef = g_hash_table_lookup(netdefs, scalar(key));
+ cur_netdef = netdefs ? g_hash_table_lookup(netdefs, scalar(key)) : NULL;
if (cur_netdef) {
/* already exists, overriding/amending previous definition */
if (cur_netdef->type != GPOINTER_TO_UINT(data))
return yaml_error(key, error, "Updated definition '%s' changes device type", scalar(key));
} else {
- netplan_netdef_new(scalar(key), GPOINTER_TO_UINT(data), backend_cur_type);
+ cur_netdef = netplan_netdef_new(scalar(key), GPOINTER_TO_UINT(data), backend_cur_type);
}
+ g_assert(cur_filename);
+ cur_netdef->filename = g_strdup(cur_filename);
// XXX: breaks multi-pass parsing.
//if (!g_hash_table_add(ids_in_file, cur_netdef->id))
@@ -2412,6 +2485,10 @@ handle_network_type(yaml_document_t* doc, yaml_node_t* node, const void* data, G
case NETPLAN_DEF_TYPE_TUNNEL: handlers = tunnel_def_handlers; break;
case NETPLAN_DEF_TYPE_VLAN: handlers = vlan_def_handlers; break;
case NETPLAN_DEF_TYPE_WIFI: handlers = wifi_def_handlers; break;
+ case NETPLAN_DEF_TYPE_NM:
+ g_warning("netplan: %s: handling NetworkManager passthrough device, settings are not fully supported.", cur_netdef->id);
+ handlers = ethernet_def_handlers;
+ break;
default: g_assert_not_reached(); // LCOV_EXCL_LINE
}
if (!process_mapping(doc, value, handlers, NULL, error))
@@ -2424,7 +2501,7 @@ handle_network_type(yaml_document_t* doc, yaml_node_t* node, const void* data, G
/* convenience shortcut: physical device without match: means match
* name on ID */
if (cur_netdef->type < NETPLAN_DEF_TYPE_VIRTUAL && !cur_netdef->has_match)
- cur_netdef->match.original_name = cur_netdef->id;
+ cur_netdef->match.original_name = g_strdup(cur_netdef->id);
}
backend_cur_type = NETPLAN_BACKEND_NONE;
return TRUE;
@@ -2468,6 +2545,7 @@ static const mapping_entry_handler network_handlers[] = {
{"vlans", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_VLAN)},
{"wifis", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_WIFI)},
{"modems", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_MODEM)},
+ {"nm-devices", YAML_MAPPING_NODE, handle_network_type, NULL, GUINT_TO_POINTER(NETPLAN_DEF_TYPE_NM)},
{"openvswitch", YAML_MAPPING_NODE, NULL, ovs_network_settings_handlers},
{NULL}
};
@@ -2545,9 +2623,6 @@ netplan_parse_yaml(const char* filename, GError** error)
if (!load_yaml(filename, &doc, error))
return FALSE;
- if (!netdefs)
- netdefs = g_hash_table_new(g_str_hash, g_str_equal);
-
/* empty file? */
if (yaml_document_get_root_node(&doc) == NULL)
return TRUE;
@@ -2555,8 +2630,10 @@ netplan_parse_yaml(const char* filename, GError** error)
g_assert(ids_in_file == NULL);
ids_in_file = g_hash_table_new(g_str_hash, NULL);
+ cur_filename = filename;
ret = process_document(&doc, error);
+ cur_filename = NULL;
cur_netdef = NULL;
yaml_document_delete(&doc);
g_hash_table_destroy(ids_in_file);
@@ -2619,6 +2696,49 @@ netplan_clear_netdefs()
/* FIXME: make sure that any dynamically allocated netdef data is freed */
if (n > 0)
g_hash_table_remove_all(netdefs);
+ netdefs = NULL;
}
+ if(netdefs_ordered) {
+ g_clear_list(&netdefs_ordered, g_free);
+ netdefs_ordered = NULL;
+ }
return n;
}
+
+void
+process_input_file(const char* f)
+{
+ GError* error = NULL;
+
+ g_debug("Processing input file %s..", f);
+ if (!netplan_parse_yaml(f, &error)) {
+ g_fprintf(stderr, "%s\n", error->message);
+ exit(1);
+ }
+}
+
+gboolean
+process_yaml_hierarchy(const char* rootdir)
+{
+ glob_t gl;
+ /* Files with asciibetically higher names override/append settings from
+ * earlier ones (in all config dirs); files in /run/netplan/
+ * shadow files in /etc/netplan/ which shadow files in /lib/netplan/.
+ * To do that, we put all found files in a hash table, then sort it by
+ * file name, and add the entries from /run after the ones from /etc
+ * and those after the ones from /lib. */
+ if (find_yaml_glob(rootdir, &gl) != 0)
+ return FALSE; // LCOV_EXCL_LINE
+ /* keys are strdup()ed, free them; values point into the glob_t, don't free them */
+ g_autoptr(GHashTable) configs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+ g_autoptr(GList) config_keys = NULL;
+
+ for (size_t i = 0; i < gl.gl_pathc; ++i)
+ g_hash_table_insert(configs, g_path_get_basename(gl.gl_pathv[i]), gl.gl_pathv[i]);
+
+ config_keys = g_list_sort(g_hash_table_get_keys(configs), (GCompareFunc) strcmp);
+
+ for (GList* i = config_keys; i != NULL; i = i->next)
+ process_input_file(g_hash_table_lookup(configs, i->data));
+ return TRUE;
+}
diff --git a/src/parse.h b/src/parse.h
index 9cbc74d..19abf10 100644
--- a/src/parse.h
+++ b/src/parse.h
@@ -51,6 +51,9 @@ typedef enum {
NETPLAN_DEF_TYPE_VLAN,
NETPLAN_DEF_TYPE_TUNNEL,
NETPLAN_DEF_TYPE_PORT,
+ /* Type fallback/passthrough */
+ NETPLAN_DEF_TYPE_NM,
+ NETPLAN_DEF_TYPE_MAX_
} NetplanDefType;
typedef enum {
@@ -218,6 +221,19 @@ typedef struct ovs_settings {
NetplanAuthenticationSettings ssl;
} NetplanOVSSettings;
+typedef union {
+ struct NetplanNMSettings {
+ char *name;
+ char *uuid;
+ char *stable_id;
+ char *device;
+ GData* passthrough;
+ } nm;
+ struct NetplanNetworkdSettings {
+ char *unit;
+ } networkd;
+} NetplanBackendSettings;
+
/**
* Represent a configuration stanza
*/
@@ -230,6 +246,7 @@ struct net_definition {
NetplanDefType type;
NetplanBackend backend;
char* id;
+ char* filename;
/* only necessary for NetworkManager connection UUIDs in some cases */
uuid_t uuid;
@@ -375,25 +392,24 @@ struct net_definition {
/* netplan-feature: openvswitch */
NetplanOVSSettings ovs_settings;
- union {
- struct NetplanNMSettings {
- char *name;
- char *uuid;
- char *stable_id;
- char *device;
- } nm;
- struct NetplanNetworkdSettings {
- char *unit;
- } networkd;
- } backend_settings;
+ NetplanBackendSettings backend_settings;
};
typedef enum {
NETPLAN_WIFI_MODE_INFRASTRUCTURE,
NETPLAN_WIFI_MODE_ADHOC,
- NETPLAN_WIFI_MODE_AP
+ NETPLAN_WIFI_MODE_AP,
+ NETPLAN_WIFI_MODE_OTHER,
+ NETPLAN_WIFI_MODE_MAX_
} NetplanWifiMode;
+static const char* const netplan_wifi_mode_to_str[NETPLAN_WIFI_MODE_MAX_] = {
+ [NETPLAN_WIFI_MODE_INFRASTRUCTURE] = "infrastructure",
+ [NETPLAN_WIFI_MODE_ADHOC] = "adhoc",
+ [NETPLAN_WIFI_MODE_AP] = "ap",
+ [NETPLAN_WIFI_MODE_OTHER] = NULL,
+};
+
typedef struct {
char *endpoint;
char *public_key;
@@ -424,6 +440,8 @@ typedef struct {
NetplanAuthenticationSettings auth;
gboolean has_auth;
+
+ NetplanBackendSettings backend_settings;
} NetplanWifiAccessPoint;
#define NETPLAN_ADVERTISED_RECEIVE_WINDOW_UNSPEC 0
@@ -482,5 +500,10 @@ extern NetplanOVSSettings ovs_settings_global;
gboolean netplan_parse_yaml(const char* filename, GError** error);
GHashTable* netplan_finish_parse(GError** error);
+guint netplan_clear_netdefs();
NetplanBackend netplan_get_global_backend();
const char* tunnel_mode_to_string(NetplanTunnelMode mode);
+NetplanNetDefinition* netplan_netdef_new(const char* id, NetplanDefType type, NetplanBackend renderer);
+
+void process_input_file(const char* f);
+gboolean process_yaml_hierarchy(const char* rootdir);
diff --git a/src/util.c b/src/util.c
index 7e59985..7dc9ba6 100644
--- a/src/util.c
+++ b/src/util.c
@@ -22,6 +22,7 @@
#include <glib/gprintf.h>
#include "util.h"
+#include "netplan.h"
GHashTable* wifi_frequency_24;
GHashTable* wifi_frequency_5;
@@ -51,10 +52,12 @@ safe_mkdir_p_dir(const char* file_path)
void g_string_free_to_file(GString* s, const char* rootdir, const char* path, const char* suffix)
{
g_autofree char* full_path = NULL;
+ g_autofree char* path_suffix = NULL;
g_autofree char* contents = g_string_free(s, FALSE);
GError* error = NULL;
- full_path = g_strjoin(NULL, rootdir ?: "", G_DIR_SEPARATOR_S, path, suffix, NULL);
+ path_suffix = g_strjoin(NULL, path, suffix, NULL);
+ full_path = g_build_path(G_DIR_SEPARATOR_S, rootdir ?: G_DIR_SEPARATOR_S, path_suffix, NULL);
safe_mkdir_p_dir(full_path);
if (!g_file_set_contents(full_path, contents, -1, &error)) {
/* the mkdir() just succeeded, there is no sensible
@@ -195,3 +198,94 @@ systemd_escape(char* string)
return escaped;
}
+
+gboolean
+netplan_delete_connection(const char* id, const char* rootdir)
+{
+ g_autofree gchar* filename = NULL;
+ g_autofree gchar* del = NULL;
+ g_autoptr(GError) error = NULL;
+ NetplanNetDefinition* nd = NULL;
+
+ /* parse all YAML files */
+ if (!process_yaml_hierarchy(rootdir))
+ return FALSE; // LCOV_EXCL_LINE
+
+ netdefs = netplan_finish_parse(&error);
+ if (!netdefs) {
+ // LCOV_EXCL_START
+ g_fprintf(stderr, "netplan_delete_connection: %s\n", error->message);
+ return FALSE;
+ // LCOV_EXCL_STOP
+ }
+
+ /* find filename for specified netdef ID */
+ nd = g_hash_table_lookup(netdefs, id);
+ if (!nd) {
+ g_warning("netplan_delete_connection: Cannot delete %s, does not exist.", id);
+ return FALSE;
+ }
+
+ filename = g_path_get_basename(nd->filename);
+ filename[strlen(filename) - 5] = '\0'; //stip ".yaml" suffix
+ del = g_strdup_printf("network.%s.%s=NULL", netplan_def_type_to_str[nd->type], id);
+ netplan_clear_netdefs();
+
+ /* TODO: refactor logic to actually be inside the library instead of spawning another process */
+ const gchar *argv[] = { "/sbin/netplan", "set", del, "--origin-hint" , filename, NULL, NULL, NULL };
+ if (rootdir) {
+ argv[5] = "--root-dir";
+ argv[6] = rootdir;
+ }
+ if (getenv("TEST_NETPLAN_CMD") != 0)
+ argv[0] = getenv("TEST_NETPLAN_CMD");
+ return g_spawn_sync(NULL, (gchar**)argv, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+}
+
+gboolean
+netplan_generate(const char* rootdir)
+{
+ /* TODO: refactor logic to actually be inside the library instead of spawning another process */
+ const gchar *argv[] = { "/sbin/netplan", "generate", NULL , NULL, NULL };
+ if (rootdir) {
+ argv[2] = "--root-dir";
+ argv[3] = rootdir;
+ }
+ if (getenv("TEST_NETPLAN_CMD") != 0)
+ argv[0] = getenv("TEST_NETPLAN_CMD");
+ return g_spawn_sync(NULL, (gchar**)argv, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
+}
+
+/**
+ * Extract the netplan netdef ID from a NetworkManager connection profile (keyfile),
+ * generated by netplan. Used by the NetworkManager YAML backend.
+ */
+gchar*
+netplan_get_id_from_nm_filename(const char* filename, const char* ssid)
+{
+ g_autofree gchar* escaped_ssid = NULL;
+ g_autofree gchar* suffix = NULL;
+ const char* nm_prefix = "/run/NetworkManager/system-connections/netplan-";
+ const char* pos = g_strrstr(filename, nm_prefix);
+ const char* start = NULL;
+ const char* end = NULL;
+ gsize id_len = 0;
+
+ if (!pos)
+ return NULL;
+
+ if (ssid) {
+ escaped_ssid = g_uri_escape_string(ssid, NULL, TRUE);
+ suffix = g_strdup_printf("-%s.nmconnection", escaped_ssid);
+ end = g_strrstr(filename, suffix);
+ } else
+ end = g_strrstr(filename, ".nmconnection");
+
+ if (!end)
+ return NULL;
+
+ /* Move pointer to start of netplan ID inside filename string */
+ start = pos + strlen(nm_prefix);
+ id_len = end - start;
+ return g_strndup(start, id_len);
+}
diff --git a/src/util.h b/src/util.h
index b938147..5f6fd81 100644
--- a/src/util.h
+++ b/src/util.h
@@ -31,5 +31,8 @@ int wifi_get_freq24(int channel);
int wifi_get_freq5(int channel);
gchar* systemd_escape(char* string);
+gboolean netplan_delete_connection(const char* id, const char* rootdir);
+gboolean netplan_generate(const char* rootdir);
+gchar* netplan_get_id_from_nm_filename(const char* filename, const char* ssid);
#define OPENVSWITCH_OVS_VSCTL "/usr/bin/ovs-vsctl"
diff --git a/src/validation.c b/src/validation.c
index 3ec5859..5cdfb15 100644
--- a/src/validation.c
+++ b/src/validation.c
@@ -342,6 +342,9 @@ validate_netdef_grammar(NetplanNetDefinition* nd, yaml_node_t* node, GError** er
// LCOV_EXCL_STOP
}
+ if (nd->type == NETPLAN_DEF_TYPE_NM && (!nd->backend_settings.nm.passthrough || !g_datalist_get_data(&nd->backend_settings.nm.passthrough, "connection.type")))
+ return yaml_error(node, error, "%s: network type 'nm-devices:' needs to provide a 'connection.type' via passthrough", nd->id);
+
valid = TRUE;
netdef_grammar_error: