diff --git a/.github/workflows/rpmbuild.yml b/.github/workflows/rpmbuild.yml index b8d80df6e..9e3d407b2 100644 --- a/.github/workflows/rpmbuild.yml +++ b/.github/workflows/rpmbuild.yml @@ -31,6 +31,7 @@ jobs: dnf -y install epel-release || true dnf config-manager --set-enabled crb || true # Meson/CMocka on EL9 dnf -y install centos-release-nfv-openvswitch || true # OVS on EL9 + dnf -y install gdb dnf -y builddep rpm/netplan.spec adduser test chown -R test:test . diff --git a/doc/netplan-everywhere.md b/doc/netplan-everywhere.md index 8d2c353f0..6f59c261a 100644 --- a/doc/netplan-everywhere.md +++ b/doc/netplan-everywhere.md @@ -101,12 +101,14 @@ network: uuid: "0f7a33ac-512e-4c03-b088-4db00fe3292e" name: "Ethernet connection 1" passthrough: - ethernet._: "" - ipv4.ignore-auto-dns: "true" - ipv6.addr-gen-mode: "default" - ipv6.method: "disabled" - ipv6.ip6-privacy: "-1" - proxy._: "" + ethernet: {} + ipv4: + ignore-auto-dns: "true" + ipv6: + addr-gen-mode: "default" + method: "disabled" + ip6-privacy: "-1" + proxy: {} ``` All the configuration under the `passthrough` mapping is added to @@ -126,17 +128,21 @@ network: uuid: "db5f0f67-1f4c-4d59-8ab8-3d278389cf87" name: "myvpnconnection" passthrough: - connection.type: "vpn" - vpn.ca: "path to ca.crt" - vpn.cert: "path to client.crt" - vpn.cipher: "AES-256-GCM" - vpn.connection-type: "tls" - vpn.dev: "tun" - vpn.key: "path to client.key" - vpn.remote: "1.2.3.4:1194" - vpn.service-type: "org.freedesktop.NetworkManager.openvpn" - ipv4.method: "auto" - ipv6.addr-gen-mode: "default" - ipv6.method: "auto" - proxy._: "" + connection: + type: "vpn" + vpn: + ca: "path to ca.crt" + cert: "path to client.crt" + cipher: "AES-256-GCM" + connection-type: "tls" + dev: "tun" + key: "path to client.key" + remote: "1.2.3.4:1194" + service-type: "org.freedesktop.NetworkManager.openvpn" + ipv4: + method: "auto" + ipv6: + addr-gen-mode: "default" + method: "auto" + proxy: {} ``` diff --git a/doc/netplan-yaml.md b/doc/netplan-yaml.md index 2857e7eab..668065be3 100644 --- a/doc/netplan-yaml.md +++ b/doc/netplan-yaml.md @@ -2146,15 +2146,17 @@ network: uuid: "db5f0f67-1f4c-4d59-8ab8-3d278389cf87" name: "myvpnconnection" passthrough: - connection.type: "vpn" - vpn.ca: "path to ca.crt" - vpn.cert: "path to client.crt" - vpn.cipher: "AES-256-GCM" - vpn.connection-type: "tls" - vpn.dev: "tun" - vpn.key: "path to client.key" - vpn.remote: "1.2.3.4:1194" - vpn.service-type: "org.freedesktop.NetworkManager.openvpn" + connection: + type: "vpn" + vpn: + ca: "path to ca.crt" + cert: "path to client.crt" + cipher: "AES-256-GCM" + connection-type: "tls" + dev: "tun" + key: "path to client.key" + remote: "1.2.3.4:1194" + service-type: "org.freedesktop.NetworkManager.openvpn" ``` ## Back end-specific configuration parameters diff --git a/src/abi.h b/src/abi.h index b4b19453c..7c3505474 100644 --- a/src/abi.h +++ b/src/abi.h @@ -211,7 +211,7 @@ typedef struct netplan_backend_settings { char *uuid; char *stable_id; char *device; - GData* passthrough; /* See g_datalist* functions */ + GHashTable* passthrough; } NetplanBackendSettings; typedef struct netplan_vxlan NetplanVxlan; diff --git a/src/netplan.c b/src/netplan.c index d6ab84902..a119eb874 100644 --- a/src/netplan.c +++ b/src/netplan.c @@ -340,14 +340,24 @@ typedef struct { } _passthrough_handler_data; STATIC void -_passthrough_handler(GQuark key_id, gpointer value, gpointer user_data) +_passthrough_handler_key_value(gpointer key, gpointer value, gpointer user_data) { _passthrough_handler_data *d = user_data; - const gchar* key = g_quark_to_string(key_id); YAML_NONNULL_STRING(d->event, d->emitter, key, value); err_path: return; // LCOV_EXCL_LINE } +STATIC void +_passthrough_handler(gpointer key, gpointer value, gpointer user_data) +{ + _passthrough_handler_data *d = user_data; + YAML_SCALAR_PLAIN(d->event, d->emitter, key); + YAML_MAPPING_OPEN(d->event, d->emitter); + g_hash_table_foreach(value, _passthrough_handler_key_value, user_data); + YAML_MAPPING_CLOSE(d->event, d->emitter); +err_path: return; // LCOV_EXCL_LINE +} + STATIC gboolean write_backend_settings(yaml_event_t* event, yaml_emitter_t* emitter, NetplanBackendSettings s) { if (s.uuid || s.name || s.passthrough) { @@ -356,13 +366,13 @@ write_backend_settings(yaml_event_t* event, yaml_emitter_t* emitter, NetplanBack YAML_NONNULL_STRING(event, emitter, "uuid", s.uuid); YAML_NONNULL_STRING(event, emitter, "name", s.name); - if (s.passthrough) { + if (s.passthrough != NULL && g_hash_table_size(s.passthrough) > 0) { 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.passthrough, _passthrough_handler, &d); + g_hash_table_foreach(s.passthrough, _passthrough_handler, &d); YAML_MAPPING_CLOSE(event, emitter); } YAML_MAPPING_CLOSE(event, emitter); diff --git a/src/nm.c b/src/nm.c index 3dbb85bfc..daa4af5e5 100644 --- a/src/nm.c +++ b/src/nm.c @@ -98,8 +98,12 @@ type_str(const NetplanNetDefinition* def) case NETPLAN_DEF_TYPE_NM: /* needs to be overriden by passthrough "connection.type" setting */ g_assert(def->backend_settings.passthrough != NULL); - GData *passthrough = def->backend_settings.passthrough; - return g_datalist_get_data(&passthrough, "connection.type"); + GHashTable *passthrough = def->backend_settings.passthrough; + GHashTable* connection = g_hash_table_lookup(passthrough, "connection"); + if (connection) { + return g_hash_table_lookup(connection, "type"); + } + return NULL; // LCOV_EXCL_START default: g_assert_not_reached(); @@ -566,50 +570,42 @@ write_nm_vxlan_parameters(const NetplanNetDefinition* def, GKeyFile* kf) * "backend_settings.passthrough" and inject them into the keyfile as-is. */ STATIC void -write_fallback_key_value(GQuark key_id, gpointer value, gpointer user_data) +write_fallback_key_value(gpointer group, 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 - } + GHashTableIter iter; + GHashTable* group_settings = value; + gpointer k, v; - 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 placeholder 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); - /* handle differing defaults: - * ipv6.ip6-privacy is "-1 (unknown)" by default in NM, it is "0 (off)" in netplan */ - else if (g_strcmp0(key, "ipv6.ip6-privacy") == 0 && g_strcmp0(val, "-1") == 0) { - g_debug("NetworkManager: default override: clearing %s.%s", group, k); - 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) != 0) { - g_debug("NetworkManager: fallback override: %s.%s=%s", group, k, val); - g_key_file_set_comment(kf, group, k, "Netplan: passthrough override", NULL); + /* + * An empty hash table means it's an empty group. + * Here we add and remove a bogus key so the group is created in the kf + */ + if (g_hash_table_size(group_settings) == 0) { + g_key_file_set_string(kf, group, NETPLAN_NM_EMPTY_GROUP, ""); + g_key_file_remove_key(kf, group, NETPLAN_NM_EMPTY_GROUP, NULL); + return; } - g_strfreev(group_key); + g_hash_table_iter_init(&iter, group_settings); + + while (g_hash_table_iter_next(&iter, &k, &v)) { + gboolean has_key = g_key_file_has_key(kf, group, k, NULL); + g_autofree gchar* old_key = g_key_file_get_string(kf, group, k, NULL); + g_key_file_set_string(kf, group, k, v); + /* handle differing defaults: + * ipv6.ip6-privacy is "-1 (unknown)" by default in NM, it is "0 (off)" in netplan */ + if (g_strcmp0(k, "ip6-privacy") == 0 && g_strcmp0(v, "-1") == 0) { + g_debug("NetworkManager: default override: clearing %s.%s", (gchar*)group, (gchar*)k); + g_key_file_remove_key(kf, group, k, NULL); + } else if (!has_key) { + g_debug("NetworkManager: passing through fallback key: %s.%s=%s", (gchar*)group, (gchar*)k, (gchar*)v); + g_key_file_set_comment(kf, group, k, "Netplan: passthrough setting", NULL); + } else if (g_strcmp0(v, old_key) != 0) { + g_debug("NetworkManager: fallback override: %s.%s=%s", (gchar*)group, (gchar*)k, (gchar*)v); + g_key_file_set_comment(kf, group, k, "Netplan: passthrough override", NULL); + } + } } /** @@ -641,6 +637,12 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, else g_assert(ap == NULL); + nm_type = type_str(def); + if (def->type == NETPLAN_DEF_TYPE_NM && nm_type == NULL) { + g_set_error(error, NETPLAN_BACKEND_ERROR, NETPLAN_ERROR_UNSUPPORTED, "ERROR: %s: NetworkManager connection type undefined\n", def->id); + return FALSE; + } + if (def->type == NETPLAN_DEF_TYPE_VLAN && def->sriov_vlan_filter) { g_debug("%s is defined as a hardware SR-IOV filtered VLAN, postponing creation", def->id); return TRUE; @@ -660,7 +662,6 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, g_key_file_set_string(kf, "connection", "id", nd_nm_id); } - nm_type = type_str(def); if (nm_type && def->type != NETPLAN_DEF_TYPE_NM) g_key_file_set_string(kf, "connection", "type", nm_type); @@ -932,7 +933,7 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, 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((GData**)&def->backend_settings.passthrough, write_fallback_key_value, kf); + g_hash_table_foreach(def->backend_settings.passthrough, write_fallback_key_value, kf); } g_autofree char* escaped_netdef_id = g_uri_escape_string(def->id, NULL, TRUE); @@ -970,7 +971,7 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, * 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.passthrough, write_fallback_key_value, kf); + g_hash_table_foreach(ap->backend_settings.passthrough, write_fallback_key_value, kf); } } else { /* TODO: make use of netplan_netdef_get_output_filename() */ @@ -1089,11 +1090,21 @@ netplan_state_finish_nm_write( GString *tmp = NULL; guint unmanaged = nd->backend == NETPLAN_BACKEND_NM ? 0 : 1; + if (nd->type == NETPLAN_DEF_TYPE_NM_PLACEHOLDER_ || nd->backend == NETPLAN_BACKEND_OVS) { + iter = iter->next; + continue; + } + + nm_type = type_str(nd); + if (nd->type == NETPLAN_DEF_TYPE_NM && nm_type == NULL) { + /* Will happen when errors are ignored */ + iter = iter->next; + continue; + } + g_autofree char* netdef_id = _netplan_scrub_string(nd->id); /* Special case: manage or ignore any device of given type on empty "match: {}" stanza */ if (nd->has_match && !nd->match.driver && !nd->match.mac && !nd->match.original_name) { - nm_type = type_str(nd); - g_assert(nm_type != NULL); g_string_append_printf(nm_conf, "[device-netplan.%s.%s]\nmatch-device=type:%s\n" "managed=%d\n\n", netplan_def_type_name(nd->type), netdef_id, nm_type, !unmanaged); diff --git a/src/parse-nm.c b/src/parse-nm.c index 3708f594b..67f87a7ad 100644 --- a/src/parse-nm.c +++ b/src/parse-nm.c @@ -448,39 +448,44 @@ parse_bond_arp_ip_targets(GKeyFile* kf, GArray **targets_arr) /* Read the key-value pairs from the keyfile and pass them through to a map */ STATIC void -read_passthrough(GKeyFile* kf, GData** list) +read_passthrough(GKeyFile* kf, GHashTable** list) { gchar **groups = NULL; gchar **keys = NULL; - gchar *group_key = NULL; gchar *value = NULL; gsize klen = 0; gsize glen = 0; + GHashTable* group; - if (!*list) - g_datalist_init(list); + if (*list == NULL) { + *list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + } groups = g_key_file_get_groups(kf, &glen); - if (groups) { + if (groups != NULL) { for (unsigned i = 0; i < glen; ++i) { klen = 0; + group = g_hash_table_lookup(*list, groups[i]); + if (group == NULL) { + group = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + } 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); + g_hash_table_insert(*list, g_strdup(groups[i]), group); + g_strfreev(keys); continue; } for (unsigned j = 0; j < klen; ++j) { value = g_key_file_get_string(kf, groups[i], keys[j], NULL); - if (!value) { + if (value == NULL) { // 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); - g_free(group_key); + g_hash_table_insert(group, g_strdup(keys[j]), value); } + g_hash_table_insert(*list, g_strdup(groups[i]), group); g_strfreev(keys); } g_strfreev(groups); diff --git a/src/parse.c b/src/parse.c index 6500c7a9c..eaa259192 100644 --- a/src/parse.c +++ b/src/parse.c @@ -527,43 +527,116 @@ handle_generic_map(NetplanParser *npp, yaml_node_t* node, const char* key_prefix } /* - * Handler for setting a DataList field from a mapping node, inside a given struct + * Handle legacy networkmanager.passthrough format: + * group_name.key: value + * + * See https://github.com/canonical/netplan/pull/522 +*/ +STATIC void +handle_passthrough_legacy_scalar_settings(gchar* key, yaml_node_t* settings, GHashTable* groups) +{ + g_autofree char* escaped_value = g_strescape(scalar(settings), STRESCAPE_EXCEPTIONS); + + /* 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: + */ + + gchar **group_key = g_strsplit(key, ".", -1); + guint len = g_strv_length(group_key); + g_autofree gchar* old_key = NULL; + g_autofree gchar* k = NULL; + g_autofree gchar* group = NULL; + + if (len < 2) { + g_warning("NetworkManager: passthrough key '%s' format is invalid, should be 'group.key'.", key); + g_strfreev(group_key); + return; + } + 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 + } + + if (!g_hash_table_contains(groups, group)) { + GHashTable *keys = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + /* Only add to the hash map if it's not the empty group */ + if (g_strcmp0(k, NETPLAN_NM_EMPTY_GROUP)) { + g_hash_table_insert(keys, g_strdup(k), g_strdup(escaped_value)); + } + g_hash_table_insert(groups, g_strdup(group), keys); + } else { + GHashTable *keys = g_hash_table_lookup(groups, group); + + /* If we find the group again and it's empty, drop all the items from the group */ + if (!g_strcmp0(k, NETPLAN_NM_EMPTY_GROUP)) { + g_hash_table_remove_all(keys); + } else { + g_hash_table_insert(keys, g_strdup(k), g_strdup(escaped_value)); + } + } + g_strfreev(group_key); +} + +/* + * Handler for setting a Hashmap 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(NetplanParser *npp, yaml_node_t* node, const char* key_prefix, void* entryptr, const void* data, GError** error) +handle_passthrough(NetplanParser *npp, yaml_node_t* node, const char* key_prefix, 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); + GHashTable** groups = (GHashTable**) ((void*) entryptr + offset); + if (*groups == NULL) { + *groups = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + } for (yaml_node_pair_t* entry = node->data.mapping.pairs.start; entry < node->data.mapping.pairs.top; entry++) { yaml_node_t* key, *value; g_autofree char* full_key = NULL; g_autofree char* escaped_key = NULL; - g_autofree char* escaped_value = NULL; key = yaml_document_get_node(&npp->doc, entry->key); value = yaml_document_get_node(&npp->doc, entry->value); - assert_type(npp, key, YAML_SCALAR_NODE); - assert_type(npp, value, YAML_SCALAR_NODE); - escaped_key = g_strescape(scalar(key), STRESCAPE_EXCEPTIONS); - escaped_value = g_strescape(scalar(value), STRESCAPE_EXCEPTIONS); - if (npp->null_fields && key_prefix) { + if (npp->null_fields != NULL && key_prefix != NULL) { full_key = g_strdup_printf("%s\t%s", key_prefix, escaped_key); - if (g_hash_table_contains(npp->null_fields, full_key)) + if (g_hash_table_contains(npp->null_fields, full_key)) { continue; + } } - g_datalist_id_set_data_full(list, g_quark_from_string(escaped_key), - g_strdup(escaped_value), g_free); + if (value->type == YAML_SCALAR_NODE) { + handle_passthrough_legacy_scalar_settings(escaped_key, value, *groups); + } else if (value->type == YAML_MAPPING_NODE) { + /* + * Handle new networkmanager.passthrough format: + * group_name: + * key: value + * + * See https://github.com/canonical/netplan/pull/522 + */ + GHashTable* group = g_hash_table_lookup(*groups, escaped_key); + if (group == NULL) { + group = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert(*groups, g_strdup(escaped_key), group); + } + handle_generic_map(npp, value, full_key, &group, NULL, error); + if (*error != NULL) { + g_debug("Passthrough handling error, ignoring...: %s", (*error)->message); + netplan_error_clear(error); + } + } } - mark_data_as_dirty(npp, list); + mark_data_as_dirty(npp, groups); return TRUE; } @@ -864,47 +937,11 @@ handle_netdef_use_domains(NetplanParser* npp, yaml_node_t* node, const void* dat return TRUE; } -/* - * Check if the passthrough key format is incorrect and remove it from the list. - * user_data is expected to contain a pointer to the GData list. - */ -STATIC void -validate_kf_group_key(GQuark key_id, __unused gpointer value, gpointer user_data) -{ - GArray* bad_keys = user_data; - const gchar* key = g_quark_to_string(key_id); - gchar** group_key = g_strsplit(key, ".", -1); - if (g_strv_length(group_key) < 2) { - g_warning("NetworkManager: passthrough key '%s' format is invalid, should be 'group.key'.", key); - g_array_append_val(bad_keys, key_id); - } - g_strfreev(group_key); -} - STATIC gboolean -handle_netdef_passthrough_datalist(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error) +handle_netdef_passthrough_mapping(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error) { g_assert(npp->current.netdef); - gboolean ret = handle_generic_datalist(npp, node, key_prefix, npp->current.netdef, data, error); - - GData** list = &npp->current.netdef->backend_settings.passthrough; - GArray* bad_keys = g_array_new(FALSE, FALSE, sizeof(GQuark)); - - /* Validate and remove passthrough keys that are not in the - * expected format (group.key) - */ - g_datalist_foreach(list, validate_kf_group_key, bad_keys); - - for (unsigned int i = 0; i < bad_keys->len; i++) { - GQuark bad_quark = g_array_index(bad_keys, GQuark, i); - g_datalist_id_remove_data(list, bad_quark); - } - - g_array_free(bad_keys, TRUE); - - if (*list == NULL) { - g_datalist_clear(list); - } + gboolean ret = handle_passthrough(npp, node, key_prefix, npp->current.netdef, data, error); npp->current.netdef->has_backend_settings_nm = TRUE; @@ -1102,29 +1139,10 @@ handle_ap_backend_settings_str(NetplanParser* npp, yaml_node_t* node, const void } STATIC gboolean -handle_access_point_datalist(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error) +handle_access_point_passthrough_mapping(NetplanParser* npp, yaml_node_t* node, const char* key_prefix, const void* data, GError** error) { g_assert(npp->current.access_point != NULL); - gboolean ret = handle_generic_datalist(npp, node, key_prefix, npp->current.access_point, data, error); - - GData** list = &npp->current.access_point->backend_settings.passthrough; - GArray* bad_keys = g_array_new(FALSE, FALSE, sizeof(GQuark)); - - /* Validate and remove passthrough keys that are not in the - * expected format (group.key) - */ - g_datalist_foreach(list, validate_kf_group_key, bad_keys); - - for (unsigned int i = 0; i < bad_keys->len; i++) { - GQuark bad_quark = g_array_index(bad_keys, GQuark, i); - g_datalist_id_remove_data(list, bad_quark); - } - - g_array_free(bad_keys, TRUE); - - if (*list == NULL) { - g_datalist_clear(list); - } + gboolean ret = handle_passthrough(npp, node, key_prefix, npp->current.access_point, data, error); npp->current.netdef->has_backend_settings_nm = TRUE; @@ -1241,7 +1259,7 @@ static const mapping_entry_handler nm_backend_settings_handlers[] = { {"stable-id", YAML_SCALAR_NODE, {.generic=handle_netdef_backend_settings_str}, netdef_offset(backend_settings.stable_id)}, {"device", YAML_SCALAR_NODE, {.generic=handle_netdef_backend_settings_str}, netdef_offset(backend_settings.device)}, /* Fallback mode, to support all NM settings of the NetworkManager netplan backend */ - {"passthrough", YAML_MAPPING_NODE, {.map={.custom=handle_netdef_passthrough_datalist}}, netdef_offset(backend_settings.passthrough)}, + {"passthrough", YAML_MAPPING_NODE, {.map={.custom=handle_netdef_passthrough_mapping}}, netdef_offset(backend_settings.passthrough)}, {NULL} }; @@ -1252,7 +1270,7 @@ static const mapping_entry_handler ap_nm_backend_settings_handlers[] = { {"stable-id", YAML_SCALAR_NODE, {.generic=handle_ap_backend_settings_str}, access_point_offset(backend_settings.stable_id)}, {"device", YAML_SCALAR_NODE, {.generic=handle_ap_backend_settings_str}, access_point_offset(backend_settings.device)}, /* Fallback mode, to support all NM settings of the NetworkManager netplan backend */ - {"passthrough", YAML_MAPPING_NODE, {.map={.custom=handle_access_point_datalist}}, access_point_offset(backend_settings.passthrough)}, + {"passthrough", YAML_MAPPING_NODE, {.map={.custom=handle_access_point_passthrough_mapping}}, access_point_offset(backend_settings.passthrough)}, {NULL} }; @@ -1909,7 +1927,7 @@ handle_vxlan_tristate(NetplanParser* npp, yaml_node_t* node, const void* data, G STATIC int get_ip_family(const char* address) { - g_autofree char *ip_str; + g_autofree char *ip_str = NULL; char *prefix_len; ip_str = g_strdup(address); diff --git a/src/types.c b/src/types.c index 7a1c20ed2..99944795d 100644 --- a/src/types.c +++ b/src/types.c @@ -169,6 +169,21 @@ reset_ip_rule(NetplanIPRule* ip_rule) ip_rule->fwmark = NETPLAN_IP_RULE_FW_MARK_UNSPEC; } +STATIC void +reset_passthrough(GHashTable* passthrough) +{ + GHashTableIter iter; + GHashTable* group; + gchar* group_name; + + g_hash_table_iter_init(&iter, passthrough); + + while (g_hash_table_iter_next(&iter, (gpointer) &group_name, (gpointer) &group)) { + g_hash_table_destroy(group); + } + g_hash_table_destroy(passthrough); +} + /* Reset a backend settings object. */ STATIC void reset_backend_settings(NetplanBackendSettings* settings) @@ -177,7 +192,9 @@ reset_backend_settings(NetplanBackendSettings* settings) FREE_AND_NULLIFY(settings->uuid); FREE_AND_NULLIFY(settings->stable_id); FREE_AND_NULLIFY(settings->device); - g_datalist_clear(&settings->passthrough); + if (settings->passthrough != NULL) { + reset_passthrough(settings->passthrough); + } } STATIC void diff --git a/src/validation.c b/src/validation.c index a0371a8fe..692f6a8ae 100644 --- a/src/validation.c +++ b/src/validation.c @@ -407,7 +407,14 @@ validate_netdef_grammar(const NetplanParser* npp, NetplanNetDefinition* nd, GErr // LCOV_EXCL_STOP } - if (nd->type == NETPLAN_DEF_TYPE_NM && (!nd->backend_settings.passthrough || !g_datalist_get_data(&nd->backend_settings.passthrough, "connection.type"))) + GHashTable* key = NULL; + if (nd->backend_settings.passthrough != NULL) { + GHashTable* group = g_hash_table_lookup(nd->backend_settings.passthrough, "connection"); + if (group != NULL) { + key = g_hash_table_lookup(group, "type"); + } + } + if (nd->type == NETPLAN_DEF_TYPE_NM && key == NULL) return yaml_error(npp, NULL, error, "%s: network type 'nm-devices:' needs to provide a 'connection.type' via passthrough", nd->id); if (npp->current.netdef) diff --git a/tests/cli/test_get_set.py b/tests/cli/test_get_set.py index 6d8b93ae3..3431c5466 100644 --- a/tests/cli/test_get_set.py +++ b/tests/cli/test_get_set.py @@ -464,7 +464,7 @@ def test_set_delete_access_point(self): out = yaml.safe_load(f) self.assertNotIn('Joe\'s Home', out['network']['wifis']['wl0']['access-points']) - def test_set_delete_nm_passthrough(self): + def test_set_delete_nm_ap_passthrough(self): with open(self.path, 'w') as f: f.write('''network: version: 2 @@ -479,15 +479,40 @@ def test_set_delete_nm_passthrough(self): networkmanager: name: "myid with spaces" passthrough: - connection.permissions: "" - ipv4.dns-search: ""''') + connection: + permissions: "" + ipv4: + dns-search: ""''') ap_key = 'network.wifis.wlan0.access-points.SOME-SSID' - self._set([ap_key+'.networkmanager.passthrough.connection\\.permissions=null']) + self._set([ap_key+'.networkmanager.passthrough.connection.permissions=null']) with open(self.path, 'r') as f: out = yaml.safe_load(f) ap = out['network']['wifis']['wlan0']['access-points']['SOME-SSID'] - self.assertNotIn('connection.permissions', ap['networkmanager']['passthrough']) - self.assertEqual('', ap['networkmanager']['passthrough']['ipv4.dns-search']) + self.assertNotIn('permissions', ap['networkmanager']['passthrough']['connection']) + self.assertEqual('', ap['networkmanager']['passthrough']['ipv4']['dns-search']) + + def test_set_delete_nm_passthrough(self): + with open(self.path, 'w') as f: + f.write('''network: + version: 2 + ethernets: + eth0: + renderer: NetworkManager + networkmanager: + name: "myid with spaces" + passthrough: + connection: + permissions: "" + ipv4: + dns-search: ""''') + ap_key = 'network.ethernets.eth0' + self._set([ap_key+'.networkmanager.passthrough.connection.permissions=null']) + self._set([ap_key+'.networkmanager.passthrough.ipv4=null']) + with open(self.path, 'r') as f: + out = yaml.safe_load(f) + eth0 = out['network']['ethernets']['eth0'] + self.assertNotIn('permissions', eth0['networkmanager']['passthrough']['connection']) + self.assertNotIn('ipv4', eth0['networkmanager']['passthrough']) def test_set_delete_bridge_subparams(self): with open(self.path, 'w') as f: @@ -756,3 +781,68 @@ def test_get_yaml_document_end_failure(self): # this shall not throw any (YAML DOCUMENT-END) exception out = yaml.safe_load(self._get(['ethernets.eth0'])) self.assertListEqual(['match', 'dhcp4', 'set-name', 'mtu', 'virtual-function-count'], list(out)) + + def test_get_passthrough_old_format_merging(self): + + # Test if the YAML we are emitting is in the expected new passthrough format + # when the same groups (especially empty groups) are found more than once. + # Also test if merging is working. + with open(os.path.join(self.workdir.name, 'etc', 'netplan', 'a.yaml'), 'w') as f: + f.write('''network: + version: 2 + vlans: + NM-afe79ef7-67e0-48ad-9f2a-686c85b4f538: + renderer: NetworkManager + id: 123 + link: "enx00e04c680007" + networkmanager: + uuid: "afe79ef7-67e0-48ad-9f2a-686c85b4f538" + name: "vlan-test" + passthrough: + ipv6.addr-gen-mode: "default" + ipv6.ip6-privacy: "-1" + ethernet._: "" + vlan.flags: "2" + proxy._: ""''') + + with open(os.path.join(self.workdir.name, 'etc', 'netplan', 'b.yaml'), 'w') as f: + f.write('''network: + version: 2 + vlans: + NM-afe79ef7-67e0-48ad-9f2a-686c85b4f538: + renderer: NetworkManager + id: 123 + link: "enx00e04c680007" + networkmanager: + uuid: "afe79ef7-67e0-48ad-9f2a-686c85b4f538" + name: "vlan-test" + passthrough: + ipv6.addr-gen-mode: "default" + ipv6.ip6-privacy: "-1" + ethernet._: "" + vlan.flags: "1" + proxy._: ""''') + + with open(os.path.join(self.workdir.name, 'etc', 'netplan', 'c.yaml'), 'w') as f: + f.write('''network: + version: 2 + vlans: + NM-afe79ef7-67e0-48ad-9f2a-686c85b4f538: + renderer: NetworkManager + id: 123 + link: "enx00e04c680007" + networkmanager: + uuid: "afe79ef7-67e0-48ad-9f2a-686c85b4f538" + name: "vlan-test" + passthrough: + ipv6._: "" + ethernet._: "" + vlan.flags: "1" + proxy._: ""''') + + out = yaml.safe_load(self._get(['vlans.NM-afe79ef7-67e0-48ad-9f2a-686c85b4f538.networkmanager.passthrough'])) + self.assertDictEqual({'ethernet': {}, + 'ipv6': {}, + 'proxy': {}, + 'vlan': {'flags': '1'} + }, out) diff --git a/tests/config_fuzzer/runner.sh b/tests/config_fuzzer/runner.sh index e5b9ff15b..e396fa493 100644 --- a/tests/config_fuzzer/runner.sh +++ b/tests/config_fuzzer/runner.sh @@ -121,29 +121,26 @@ echo "$(date) - Done" echo "$(date) - Running netplan generate -i" -for yaml in ${FAKEDATADIR}/*.yaml -do - rm -rf fakeroot3 - mkdir -p fakeroot3/etc/netplan - cp ${yaml} fakeroot3/etc/netplan/ +# Run the generator against the entire dataset - OUTPUT=$(${NETPLAN_GENERATE_PATH} --root-dir fakeroot3 -i 2>&1) - code=$? - if [ $code -eq 139 ] || [ $code -eq 245 ] || [ $code -eq 133 ] - then - echo "GENERATE --ignore-errors CRASHED" - cat ${yaml} - error=1 - fi +rm -rf fakeroot3 +mkdir -p fakeroot3/etc/ +mv ${FAKEDATADIR} fakeroot3/etc/netplan - if grep 'detected memory leaks' <<< "$OUTPUT" > /dev/null - then - echo "GENERATE --ignore-errors MEMORY LEAK DETECTED" - cat ${yaml} - error=1 - fi +OUTPUT=$(${NETPLAN_GENERATE_PATH} --root-dir fakeroot3 -i 2>&1) +code=$? +# code 134 happens when a g_assert() is triggered +if [ $code -eq 139 ] || [ $code -eq 245 ] || [ $code -eq 133 ] || [ $code -eq 134 ] +then + echo "GENERATE --ignore-errors CRASHED" + error=1 +fi -done +if grep 'detected memory leaks' <<< "$OUTPUT" > /dev/null +then + echo "GENERATE --ignore-errors MEMORY LEAK DETECTED" + error=1 +fi echo "$(date) - Done" diff --git a/tests/config_fuzzer/schemas/bridges.js b/tests/config_fuzzer/schemas/bridges.js index e30054f04..6170aa9a0 100644 --- a/tests/config_fuzzer/schemas/bridges.js +++ b/tests/config_fuzzer/schemas/bridges.js @@ -124,12 +124,18 @@ const bridges_schema = { properties: { eth0: { type: "integer", + minimum: 0, + maximum: 4000000000 }, eth1: { type: "integer", + minimum: 0, + maximum: 4000000000 }, eth2: { type: "integer", + minimum: 0, + maximum: 4000000000 }, } }, diff --git a/tests/config_fuzzer/schemas/common.js b/tests/config_fuzzer/schemas/common.js index 9ae0ebbfa..82ea45217 100644 --- a/tests/config_fuzzer/schemas/common.js +++ b/tests/config_fuzzer/schemas/common.js @@ -210,6 +210,11 @@ export const networkmanager_settings = { type: "string" } }, + patternProperties: { + "[azAZ09-]{1,10}": { + type: "object", + } + } } }, required: ["passthrough"] diff --git a/tests/config_fuzzer/schemas/nm-devices.js b/tests/config_fuzzer/schemas/nm-devices.js index 0e2ef9cd7..059660c36 100644 --- a/tests/config_fuzzer/schemas/nm-devices.js +++ b/tests/config_fuzzer/schemas/nm-devices.js @@ -1,4 +1,4 @@ -import common_properties, { minMaxProperties } from "./common.js"; +import { minMaxProperties, networkmanager_settings } from "./common.js"; const nmdevices_schema = { type: "object", @@ -34,29 +34,7 @@ const nmdevices_schema = { type: "string", enum: ["NetworkManager"] }, - networkmanager: { - type: "object", - additionalProperties: false, - properties: { - uuid: { - type: "string" - }, - name: { - type: "string" - }, - passthrough: { - type: "object", - additionalProperties: true, - properties: { - "connection.type": { - type: "string" - } - }, - required: ["connection.type"] - } - }, - required: ["passthrough"] - }, + ...networkmanager_settings }, required: ["networkmanager"] } diff --git a/tests/generator/test_modems.py b/tests/generator/test_modems.py index b66144efc..867b14c49 100644 --- a/tests/generator/test_modems.py +++ b/tests/generator/test_modems.py @@ -380,17 +380,21 @@ def test_modem_nm_integration_gsm_cdma(self): uuid: a08c5805-7cf5-43f7-afb9-12cb30f6eca3 name: "T-Mobile Funkadelic 2" passthrough: - connection.type: "bluetooth" - gsm.apn: "internet2.voicestream.com" - gsm.device-id: "da812de91eec16620b06cd0ca5cbc7ea25245222" - gsm.username: "george.clinton.again" - gsm.sim-operator-id: "310260" - gsm.pin: "123456" - gsm.sim-id: "89148000000060671234" - gsm.password: "parliament2" - gsm.network-id: "254098" - ipv4.method: "auto" - ipv6.method: "auto"''') + connection: + type: bluetooth + gsm: + apn: internet2.voicestream.com + device-id: da812de91eec16620b06cd0ca5cbc7ea25245222 + username: george.clinton.again + sim-operator-id: 310260 + pin: 123456 + sim-id: 89148000000060671234 + password: parliament2 + network-id: 254098 + ipv4: + method: auto + ipv6: + method: auto''') self.assert_nm({'NM-a08c5805-7cf5-43f7-afb9-12cb30f6eca3': '''[connection] id=T-Mobile Funkadelic 2 #Netplan: passthrough override @@ -400,19 +404,19 @@ def test_modem_nm_integration_gsm_cdma(self): [gsm] apn=internet2.voicestream.com #Netplan: passthrough setting -device-id=da812de91eec16620b06cd0ca5cbc7ea25245222 +password=parliament2 #Netplan: passthrough setting -username=george.clinton.again +network-id=254098 #Netplan: passthrough setting -sim-operator-id=310260 +sim-id=89148000000060671234 #Netplan: passthrough setting pin=123456 #Netplan: passthrough setting -sim-id=89148000000060671234 +username=george.clinton.again #Netplan: passthrough setting -password=parliament2 +sim-operator-id=310260 #Netplan: passthrough setting -network-id=254098 +device-id=da812de91eec16620b06cd0ca5cbc7ea25245222 [ipv4] #Netplan: passthrough override diff --git a/tests/generator/test_passthrough.py b/tests/generator/test_passthrough.py index 8e99af473..77df8d7a2 100644 --- a/tests/generator/test_passthrough.py +++ b/tests/generator/test_passthrough.py @@ -39,7 +39,7 @@ def test_passthrough_basic(self): passthrough: connection.uuid: 87749f1d-334f-40b2-98d4-55db58965f5f connection.type: ethernet - connection.permissions: ""''') + connection.permissions: ""''', skip_generated_yaml_validation=True) self.assert_nm({'NM-87749f1d-334f-40b2-98d4-55db58965f5f': '''[connection] id=some NM id @@ -60,6 +60,116 @@ def test_passthrough_basic(self): match-device=type:ethernet managed=1\n\n''') + def test_passthrough_basic_mapping(self): + self.generate('''network: + version: 2 + ethernets: + NM-87749f1d-334f-40b2-98d4-55db58965f5f: + renderer: NetworkManager + match: {} + networkmanager: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + name: some NM id + passthrough: + connection: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + type: ethernet + permissions: ""''') + + self.assert_nm({'NM-87749f1d-334f-40b2-98d4-55db58965f5f': '''[connection] +id=some NM id +type=ethernet +uuid=87749f1d-334f-40b2-98d4-55db58965f5f +#Netplan: passthrough setting +permissions= + +[ethernet] +wake-on-lan=0 + +[ipv4] +method=link-local + +[ipv6] +method=ignore +'''}, '''[device-netplan.ethernets.NM-87749f1d-334f-40b2-98d4-55db58965f5f] +match-device=type:ethernet +managed=1\n\n''') + + def test_passthrough_basic_mapping_with_duplication(self): + self.generate('''network: + version: 2 + ethernets: + NM-87749f1d-334f-40b2-98d4-55db58965f5f: + renderer: NetworkManager + match: {} + networkmanager: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + name: some NM id + passthrough: + connection: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + type: ethernet + permissions: "" + group2: + key: a + key: b''', skip_generated_yaml_validation=True) + + self.assert_nm({'NM-87749f1d-334f-40b2-98d4-55db58965f5f': '''[connection] +id=some NM id +type=ethernet +uuid=87749f1d-334f-40b2-98d4-55db58965f5f +#Netplan: passthrough setting +permissions= + +[ethernet] +wake-on-lan=0 + +[ipv4] +method=link-local + +[ipv6] +method=ignore + +[group2] +#Netplan: passthrough setting +key=a +'''}, '''[device-netplan.ethernets.NM-87749f1d-334f-40b2-98d4-55db58965f5f] +match-device=type:ethernet +managed=1\n\n''') + + def test_passthrough_basic_mapping_no_type_ignore_error(self): + out = self.generate('''network: + version: 2 + nm-devices: + NM-87749f1d-334f-40b2-98d4-55db58965f5f: + renderer: NetworkManager + match: {} + networkmanager: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + name: some NM id + passthrough: + connection: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + permissions: ""''', skip_generated_yaml_validation=True, ignore_errors=True) + + self.assertIn('network type \'nm-devices:\' needs to provide a \'connection.type\'', out) + + def test_passthrough_basic_mapping_no_connection_ignore_error(self): + out = self.generate('''network: + version: 2 + nm-devices: + NM-87749f1d-334f-40b2-98d4-55db58965f5f: + renderer: NetworkManager + match: {} + networkmanager: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + name: some NM id + passthrough: + a: + b: c''', skip_generated_yaml_validation=True, ignore_errors=True) + + self.assertIn('network type \'nm-devices:\' needs to provide a \'connection.type\'', out) + def test_passthrough_wifi(self): self.generate('''network: version: 2 @@ -75,6 +185,61 @@ def test_passthrough_wifi(self): passthrough: connection.permissions: "" wifi.ssid: SOME-SSID + "OTHER-SSID": + hidden: true''', skip_generated_yaml_validation=True) + + self.assert_nm({'NM-87749f1d-334f-40b2-98d4-55db58965f5f-SOME-SSID': '''[connection] +id=myid with spaces +type=wifi +uuid=87749f1d-334f-40b2-98d4-55db58965f5f +#Netplan: passthrough setting +permissions= + +[ipv4] +method=link-local + +[ipv6] +method=ignore + +[wifi] +ssid=SOME-SSID +mode=infrastructure +''', + 'NM-87749f1d-334f-40b2-98d4-55db58965f5f-OTHER-SSID': '''[connection] +id=netplan-NM-87749f1d-334f-40b2-98d4-55db58965f5f-OTHER-SSID +type=wifi + +[ipv4] +method=link-local + +[ipv6] +method=ignore + +[wifi] +ssid=OTHER-SSID +mode=infrastructure +hidden=true +'''}, '''[device-netplan.wifis.NM-87749f1d-334f-40b2-98d4-55db58965f5f] +match-device=type:wifi +managed=1\n\n''') + + def test_passthrough_wifi_mapping(self): + self.generate('''network: + version: 2 + wifis: + NM-87749f1d-334f-40b2-98d4-55db58965f5f: + renderer: NetworkManager + match: {} + access-points: + "SOME-SSID": + networkmanager: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + name: myid with spaces + passthrough: + connection: + permissions: "" + wifi: + ssid: SOME-SSID "OTHER-SSID": hidden: true''') @@ -121,8 +286,9 @@ def test_passthrough_type_nm_devices(self): match: {} networkmanager: passthrough: - connection.uuid: 87749f1d-334f-40b2-98d4-55db58965f5f - connection.type: dummy''') # wokeignore:rule=dummy + connection: + uuid: 87749f1d-334f-40b2-98d4-55db58965f5f + type: dummy''') # wokeignore:rule=dummy self.assert_nm({'NM-87749f1d-334f-40b2-98d4-55db58965f5f': '''[connection] id=netplan-NM-87749f1d-334f-40b2-98d4-55db58965f5f @@ -149,7 +315,38 @@ def test_passthrough_dotted_group(self): networkmanager: passthrough: connection.type: "wireguard" - wireguard-peer.some-key.endpoint: 1.2.3.4''') + wireguard-peer.some-key.endpoint: 1.2.3.4''', skip_generated_yaml_validation=True) + + self.assert_nm({'dotted-group-test': '''[connection] +id=netplan-dotted-group-test +#Netplan: passthrough setting +type=wireguard + +[ipv4] +method=link-local + +[ipv6] +method=ignore + +[wireguard-peer.some-key] +#Netplan: passthrough setting +endpoint=1.2.3.4 +'''}, '''[device-netplan.nm-devices.dotted-group-test] +match-device=type:wireguard +managed=1\n\n''') + + def test_passthrough_dotted_group_mapping(self): + self.generate('''network: + nm-devices: + dotted-group-test: + renderer: NetworkManager + match: {} + networkmanager: + passthrough: + connection: + type: "wireguard" + wireguard-peer.some-key: + endpoint: 1.2.3.4''') self.assert_nm({'dotted-group-test': '''[connection] id=netplan-dotted-group-test @@ -179,7 +376,7 @@ def test_passthrough_dotted_key(self): passthrough: tc.qdisc.root: something tc.qdisc.fff1: ":abc" - tc.filters.test: "test"''') + tc.filters.test: "test"''', skip_generated_yaml_validation=True) self.assert_nm({'dotted-key-test': '''[connection] id=netplan-dotted-key-test @@ -198,9 +395,46 @@ def test_passthrough_dotted_key(self): #Netplan: passthrough setting qdisc.root=something #Netplan: passthrough setting +filters.test=test +#Netplan: passthrough setting qdisc.fff1=:abc +'''}, '''[device-netplan.ethernets.dotted-key-test] +match-device=type:ethernet +managed=1\n\n''') + + def test_passthrough_dotted_key_mapping(self): + self.generate('''network: + ethernets: + dotted-key-test: + renderer: NetworkManager + match: {} + networkmanager: + passthrough: + tc: + qdisc.root: something + qdisc.fff1: ":abc" + filters.test: "test"''') + + self.assert_nm({'dotted-key-test': '''[connection] +id=netplan-dotted-key-test +type=ethernet + +[ethernet] +wake-on-lan=0 + +[ipv4] +method=link-local + +[ipv6] +method=ignore + +[tc] +#Netplan: passthrough setting +qdisc.root=something #Netplan: passthrough setting filters.test=test +#Netplan: passthrough setting +qdisc.fff1=:abc '''}, '''[device-netplan.ethernets.dotted-key-test] match-device=type:ethernet managed=1\n\n''') @@ -215,7 +449,8 @@ def test_passthrough_unsupported_setting(self): "SOME-SSID": # implicit "mode: infrasturcutre" networkmanager: passthrough: - wifi.mode: "mesh"''') + wifi: + mode: "mesh"''') self.assert_nm({'test-SOME-SSID': '''[connection] id=netplan-test-SOME-SSID @@ -243,7 +478,7 @@ def test_passthrough_empty_group(self): match: {} networkmanager: passthrough: - proxy._: ""''') + proxy: {}''') self.assert_nm({'test': '''[connection] id=netplan-test @@ -311,7 +546,8 @@ def test_passthrough_ip6_privacy_default(self): uuid: 626dd384-8b3d-3690-9511-192b2c79b3fd name: "netplan-eth0" passthrough: - "ipv6.ip6-privacy": "-1" + ipv6: + ip6-privacy: -1 ''') self.assert_nm({'eth0': '''[connection] diff --git a/tests/parser/test_keyfile.py b/tests/parser/test_keyfile.py index e178f7729..14e4f89da 100644 --- a/tests/parser/test_keyfile.py +++ b/tests/parser/test_keyfile.py @@ -91,9 +91,12 @@ def test_keyfile_gsm(self): uuid: "{}" name: "T-Mobile Funkadelic 2" passthrough: - gsm.home-only: "true" - ipv4.dns-search: "" - ipv6.dns-search: "" + gsm: + home-only: "true" + ipv4: + dns-search: "" + ipv6: + dns-search: "" '''.format(UUID, UUID)}) def test_keyfile_cdma(self): @@ -165,21 +168,25 @@ def test_keyfile_gsm_via_bluetooth(self): uuid: "{}" name: "T-Mobile Funkadelic 2" passthrough: - connection.type: "bluetooth" - gsm.apn: "internet2.voicestream.com" - gsm.device-id: "da812de91eec16620b06cd0ca5cbc7ea25245222" - gsm.home-only: "true" - gsm.network-id: "254098" - gsm.password: "parliament2" - gsm.pin: "123456" - gsm.sim-id: "89148000000060671234" - gsm.sim-operator-id: "310260" - gsm.username: "george.clinton.again" - ipv4.dns-search: "" - ipv4.method: "auto" - ipv6.dns-search: "" - ipv6.method: "auto" - proxy._: "" + connection: + type: "bluetooth" + proxy: {{}} + gsm: + password: "parliament2" + apn: "internet2.voicestream.com" + home-only: "true" + network-id: "254098" + sim-id: "89148000000060671234" + pin: "123456" + device-id: "da812de91eec16620b06cd0ca5cbc7ea25245222" + sim-operator-id: "310260" + username: "george.clinton.again" + ipv4: + method: "auto" + dns-search: "" + ipv6: + method: "auto" + dns-search: "" '''.format(UUID, UUID)}) def test_keyfile_method_auto(self): @@ -232,9 +239,11 @@ def test_keyfile_method_auto(self): uuid: "{}" name: "Test" passthrough: - ipv4.dns-search: "" - ipv6.dns-search: "" - proxy._: "" + proxy: {{}} + ipv4: + dns-search: "" + ipv6: + dns-search: "" '''.format(UUID, UUID)}) def test_keyfile_fail_validation(self): @@ -332,13 +341,15 @@ def test_keyfile_method_manual(self): uuid: "{}" name: "Test" passthrough: - ipv4.dns-search: "foo.local;bar.remote;" - ipv4.method: "manual" - ipv4.address1: "1.2.3.4/24,8.8.8.8" - ipv6.dns-search: "bar.local" - ipv6.route1: "dead:beef::1/128,2001:1234::2" - ipv6.route1_options: "unknown=invalid," - proxy._: "" + proxy: {{}} + ipv4: + method: "manual" + dns-search: "foo.local;bar.remote;" + address1: "1.2.3.4/24,8.8.8.8" + ipv6: + route1_options: "unknown=invalid," + dns-search: "bar.local" + route1: "dead:beef::1/128,2001:1234::2" '''.format(UUID, UUID)}) def test_keyfile_dummy(self): # wokeignore:rule=dummy @@ -456,8 +467,10 @@ def test_keyfile_type_wifi(self): uuid: "{}" name: "myid with spaces" passthrough: - connection.permissions: "" - ipv4.dns-search: "" + connection: + permissions: "" + ipv4: + dns-search: "" networkmanager: uuid: "{}" name: "myid with spaces" @@ -517,8 +530,10 @@ def _template_keyfile_type_wifi_eap(self, method): uuid: "{}" name: "testnet" passthrough: - connection.permissions: "" - ipv4.dns-search: "" + connection: + permissions: "" + ipv4: + dns-search: "" networkmanager: uuid: "{}" name: "testnet" @@ -580,7 +595,8 @@ def test_keyfile_wifi_eap_leap(self): uuid: "{}" name: "myid with spaces" passthrough: - connection.permissions: "" + connection: + permissions: "" networkmanager: uuid: "{}" name: "myid with spaces" @@ -627,7 +643,8 @@ def test_keyfile_wifi_eap_pwd(self): uuid: "{}" name: "myid with spaces" passthrough: - connection.permissions: "" + connection: + permissions: "" networkmanager: uuid: "{}" name: "myid with spaces" @@ -673,8 +690,10 @@ def test_keyfile_wifi_eap_md5_not_supported(self): uuid: "{}" name: "myid with spaces" passthrough: - connection.permissions: "" - 802-1x.eap: "md5" + connection: + permissions: "" + 802-1x: + eap: "md5" networkmanager: uuid: "{}" name: "myid with spaces" @@ -729,7 +748,8 @@ def test_keyfile_wifi_eap_psk_with_eap(self): uuid: "{}" name: "myid with spaces" passthrough: - connection.permissions: "" + connection: + permissions: "" networkmanager: uuid: "{}" name: "myid with spaces" @@ -754,7 +774,8 @@ def _template_keyfile_type_wifi(self, nd_mode, nm_mode): if nm_mode != nd_mode: wifi_mode = ''' passthrough: - wifi.mode: "{}"'''.format(nm_mode) + wifi: + mode: "{}"'''.format(nm_mode) if nd_mode != 'infrastructure': ap_mode = '\n mode: "%s"' % nd_mode self.assert_netplan({UUID: '''network: @@ -848,7 +869,8 @@ def test_keyfile_wake_on_lan(self): uuid: "{}" name: "myid with spaces" passthrough: - ethernet.wake-on-lan: "2" + ethernet: + wake-on-lan: "2" '''.format(UUID, UUID)}) def test_keyfile_wake_on_lan_nm_default(self): @@ -967,16 +989,21 @@ def test_keyfile_yaml_wifi_hotspot(self): uuid: "{}" name: "Hotspot-1" passthrough: - connection.autoconnect: "false" - connection.permissions: "" - ipv4.dns-search: "" - ipv6.addr-gen-mode: "1" - ipv6.dns-search: "" - wifi.mac-address-blacklist: "" # wokeignore:rule=blacklist - wifi-security.group: "ccmp;" - wifi-security.pairwise: "ccmp;" - wifi-security.proto: "rsn;" - proxy._: "" + connection: + permissions: "" + autoconnect: "false" + wifi: + mac-address-blacklist: "" # wokeignore:rule=blacklist + wifi-security: + proto: "rsn;" + group: "ccmp;" + pairwise: "ccmp;" + ipv4: + dns-search: "" + ipv6: + addr-gen-mode: "1" + dns-search: "" + proxy: {{}} networkmanager: uuid: "{}" name: "Hotspot-1" @@ -1041,7 +1068,8 @@ def test_keyfile_vlan(self): uuid: "{}" name: "netplan-enblue" passthrough: - connection.interface-name: "enblue" + connection: + interface-name: "enblue" '''.format(UUID, UUID)}) def test_keyfile_bridge(self): @@ -1262,10 +1290,12 @@ def test_keyfile_customer_A2(self): uuid: "{}" name: "gsm" passthrough: - ipv4.address1: "10.10.28.159/24" - ipv4.address2: "10.10.164.254/24" - ipv4.address3: "10.10.246.132/24" - ipv6.addr-gen-mode: "1" + ipv4: + address2: "10.10.164.254/24" + address1: "10.10.28.159/24" + address3: "10.10.246.132/24" + ipv6: + addr-gen-mode: "1" '''.format(UUID, UUID)}) def test_keyfile_netplan0103_compat(self): @@ -1359,18 +1389,22 @@ def test_keyfile_netplan0103_compat(self): uuid: "{}" name: "Work Wired" passthrough: - connection.autoconnect: "false" - connection.permissions: "" - connection.timestamp: "305419896" - ethernet.mac-address-blacklist: "" # wokeignore:rule=blacklist - ipv4.address1: "192.168.0.5/24,192.168.0.1" - ipv4.dns-search: "" - ipv4.method: "manual" - ipv4.route4: "3.3.3.3/6,0.0.0.0,4" - ipv4.route4_options: "cwnd=10,mtu=1492,src=1.2.3.4" - ipv6.dns-search: "wallaceandgromit.com;" - ipv6.ip6-privacy: "1" - proxy._: "" + connection: + permissions: "" + autoconnect: "false" + timestamp: "305419896" + proxy: {{}} + ipv4: + route4_options: "cwnd=10,mtu=1492,src=1.2.3.4" + method: "manual" + dns-search: "" + address1: "192.168.0.5/24,192.168.0.1" + route4: "3.3.3.3/6,0.0.0.0,4" + ethernet: + mac-address-blacklist: "" # wokeignore:rule=blacklist + ipv6: + dns-search: "wallaceandgromit.com;" + ip6-privacy: "1" '''.format(UUID, UUID)}) def test_keyfile_tunnel_regression_lp1952967(self): @@ -1413,13 +1447,16 @@ def test_keyfile_tunnel_regression_lp1952967(self): uuid: "{}" name: "IP tunnel connection 1" passthrough: - connection.autoconnect: "false" - connection.interface-name: "gre10" - connection.permissions: "" - ipv4.dns-search: "" - ipv6.dns-search: "" - ipv6.ip6-privacy: "-1" - proxy._: "" + connection: + permissions: "" + autoconnect: "false" + interface-name: "gre10" + proxy: {{}} + ipv4: + dns-search: "" + ipv6: + dns-search: "" + ip6-privacy: "-1" '''.format(UUID, UUID)}) def test_keyfile_ip6_privacy_default_netplan_0104_compat(self): @@ -1451,7 +1488,8 @@ def test_keyfile_ip6_privacy_default_netplan_0104_compat(self): uuid: "{}" name: "Test" passthrough: - ipv6.ip6-privacy: "-1" + ipv6: + ip6-privacy: "-1" '''.format(UUID, UUID)}) def test_keyfile_wpa3_sae(self): @@ -1498,8 +1536,9 @@ def test_keyfile_wpa3_sae(self): uuid: "ff9d6ebc-226d-4f82-a485-b7ff83b9607f" name: "test2" passthrough: - ipv6.ip6-privacy: "-1" - proxy._: "" + proxy: {{}} + ipv6: + ip6-privacy: "-1" networkmanager: uuid: "{}" name: "test2" @@ -1563,8 +1602,9 @@ def test_keyfile_wpa3_enterprise_eap_sha256(self): uuid: "ff9d6ebc-226d-4f82-a485-b7ff83b9607f" name: "test2" passthrough: - ipv6.ip6-privacy: "-1" - proxy._: "" + proxy: {{}} + ipv6: + ip6-privacy: "-1" networkmanager: uuid: "{}" name: "test2" @@ -1628,8 +1668,9 @@ def test_keyfile_wpa3_enterprise_eap_suite_b_192(self): uuid: "ff9d6ebc-226d-4f82-a485-b7ff83b9607f" name: "test2" passthrough: - ipv6.ip6-privacy: "-1" - proxy._: "" + proxy: {{}} + ipv6: + ip6-privacy: "-1" networkmanager: uuid: "{}" name: "test2" @@ -1687,15 +1728,19 @@ def test_keyfile_dns_search_ip4_ip6_conflict(self): uuid: "{}" name: "Work Wired" passthrough: - connection.autoconnect: "false" - connection.timestamp: "305419896" - ethernet.wake-on-lan: "1" - ipv4.method: "manual" - ipv4.address1: "192.168.0.5/24,192.168.0.1" - ipv6.addr-gen-mode: "1" - ipv6.dns-search: "wallaceandgromit.com;" - ipv6.ip6-privacy: "-1" - proxy._: "" + connection: + autoconnect: "false" + timestamp: "305419896" + proxy: {{}} + ipv4: + method: "manual" + address1: "192.168.0.5/24,192.168.0.1" + ethernet: + wake-on-lan: "1" + ipv6: + addr-gen-mode: "1" + dns-search: "wallaceandgromit.com;" + ip6-privacy: "-1" '''.format(UUID, UUID)}) def test_keyfile_nm_140_default_ethernet_group(self): @@ -1730,13 +1775,15 @@ def test_keyfile_nm_140_default_ethernet_group(self): uuid: "{}" name: "Test Write Bridge Main" passthrough: - ethernet._: "" - bridge._: "" - ipv4.address1: "1.2.3.4/24,1.1.1.1" - ipv4.method: "manual" - ipv6.addr-gen-mode: "default" - ipv6.ip6-privacy: "-1" - proxy._: "" + proxy: {{}} + ipv4: + method: "manual" + address1: "1.2.3.4/24,1.1.1.1" + ethernet: {{}} + bridge: {{}} + ipv6: + addr-gen-mode: "default" + ip6-privacy: "-1" '''.format(UUID)}) def test_multiple_eap_methods(self): @@ -1784,8 +1831,10 @@ def test_multiple_eap_methods(self): uuid: "{}" name: "MyWifi" passthrough: - wifi-security.auth-alg: "open" - 802-1x.eap: "peap;tls" + wifi-security: + auth-alg: "open" + 802-1x: + eap: "peap;tls" networkmanager: uuid: "{}" name: "MyWifi" @@ -1836,7 +1885,8 @@ def test_single_eap_method(self): uuid: "{}" name: "MyWifi" passthrough: - wifi-security.auth-alg: "open" + wifi-security: + auth-alg: "open" networkmanager: uuid: "{}" name: "MyWifi" @@ -1862,7 +1912,8 @@ def test_simple_wireguard(self): uuid: "{}" name: "wg0" passthrough: - connection.interface-name: "wg0" + connection: + interface-name: "wg0" '''.format(UUID, UUID)}) def test_wireguard_with_key(self): @@ -1890,7 +1941,8 @@ def test_wireguard_with_key(self): uuid: "{}" name: "wg0" passthrough: - connection.interface-name: "wg0" + connection: + interface-name: "wg0" '''.format(UUID, UUID)}) def test_wireguard_with_key_and_peer(self): @@ -1928,7 +1980,8 @@ def test_wireguard_with_key_and_peer(self): uuid: "{}" name: "wg0" passthrough: - connection.interface-name: "wg0" + connection: + interface-name: "wg0" '''.format(UUID, UUID)}) def test_wireguard_with_empty_endpoint(self): @@ -1965,7 +2018,8 @@ def test_wireguard_with_empty_endpoint(self): uuid: "{}" name: "wg0" passthrough: - connection.interface-name: "wg0" + connection: + interface-name: "wg0" '''.format(UUID, UUID)}) def test_wireguard_allowed_ips_without_prefix(self): @@ -2008,7 +2062,8 @@ def test_wireguard_allowed_ips_without_prefix(self): uuid: "{}" name: "wg0" passthrough: - connection.interface-name: "wg0" + connection: + interface-name: "wg0" '''.format(UUID, UUID)}) def test_wireguard_with_key_flags(self): @@ -2049,7 +2104,8 @@ def test_wireguard_with_key_flags(self): uuid: "{}" name: "wg0" passthrough: - connection.interface-name: "wg0" + connection: + interface-name: "wg0" '''.format(UUID, UUID)}) def test_wireguard_with_key_all_flags_enabled(self): @@ -2092,7 +2148,8 @@ def test_wireguard_with_key_all_flags_enabled(self): uuid: "{}" name: "wg0" passthrough: - connection.interface-name: "wg0" + connection: + interface-name: "wg0" '''.format(UUID, UUID)}) def test_wireguard_with_key_and_peer_without_allowed_ips(self): @@ -2127,7 +2184,8 @@ def test_wireguard_with_key_and_peer_without_allowed_ips(self): uuid: "{}" name: "wg0" passthrough: - connection.interface-name: "wg0" + connection: + interface-name: "wg0" '''.format(UUID, UUID)}) def test_vxlan_with_local_and_remote(self): @@ -2158,7 +2216,8 @@ def test_vxlan_with_local_and_remote(self): uuid: "{}" name: "vxlan10" passthrough: - connection.interface-name: "vxlan10" + connection: + interface-name: "vxlan10" '''.format(UUID, UUID)}) def test_simple_vxlan(self): @@ -2185,7 +2244,8 @@ def test_simple_vxlan(self): uuid: "{}" name: "vxlan10" passthrough: - connection.interface-name: "vxlan10" + connection: + interface-name: "vxlan10" '''.format(UUID, UUID)}) def test_invalid_tunnel_mode(self): @@ -2238,7 +2298,8 @@ def test_keyfile_wifi_random_cloned_mac_address(self): uuid: "{}" name: "myid with spaces" passthrough: - ipv4.dns-search: "" + ipv4: + dns-search: "" networkmanager: uuid: "{}" name: "myid with spaces" @@ -2271,7 +2332,8 @@ def test_keyfile_ethernet_random_cloned_mac_address(self): uuid: "{}" name: "myid with spaces" passthrough: - ipv4.dns-search: "" + ipv4: + dns-search: "" '''.format(UUID, UUID)}) def test_veth_pair(self): @@ -2297,7 +2359,8 @@ def test_veth_pair(self): uuid: "{}" name: "veth-peer1" passthrough: - connection.interface-name: "veth-peer1" + connection: + interface-name: "veth-peer1" '''.format(UUID, UUID)}) def test_veth_without_peer(self): @@ -2337,7 +2400,8 @@ def test_vrf_basic(self): uuid: "{}" name: "vrf0" passthrough: - connection.interface-name: "vrf0" + connection: + interface-name: "vrf0" '''.format(UUID, UUID)}) def test_vrf_without_table_should_fail(self): @@ -2422,11 +2486,13 @@ def test_nameserver_with_DoT_lp2055148(self): uuid: "{}" name: "ethernet-eth123" passthrough: - ethernet._: "" - ipv4.dns: "8.8.8.8;1.1.1.1#lxd;192.168.0.1#domain.local;" - ipv6.addr-gen-mode: "default" - ipv6.ip6-privacy: "-1" - proxy._: "" + proxy: {{}} + ipv4: + dns: "8.8.8.8;1.1.1.1#lxd;192.168.0.1#domain.local;" + ethernet: {{}} + ipv6: + addr-gen-mode: "default" + ip6-privacy: "-1" '''.format(UUID, UUID)}) def test_ipv4_route_metric_is_overriden_when_dhcp4_is_disabled(self): @@ -2461,12 +2527,15 @@ def test_ipv4_route_metric_is_overriden_when_dhcp4_is_disabled(self): uuid: "{}" name: "dummy-123" passthrough: - connection.interface-name: "dummy123" - ipv4.method: "manual" - ipv4.address1: "100.85.0.1/24,100.85.0.1" - ipv6.addr-gen-mode: "default" - dummy._: "" - proxy._: "" + connection: + interface-name: "dummy123" + proxy: {{}} + ipv4: + method: "manual" + address1: "100.85.0.1/24,100.85.0.1" + ipv6: + addr-gen-mode: "default" + dummy: {{}} '''.format(UUID, UUID)}) def test_ipv6_route_metric_is_overriden_when_dhcp6_is_disabled(self): @@ -2501,12 +2570,15 @@ def test_ipv6_route_metric_is_overriden_when_dhcp6_is_disabled(self): uuid: "{}" name: "dummy-123" passthrough: - connection.interface-name: "dummy123" - ipv4.method: "disabled" - ipv6.method: "manual" - ipv6.address1: "fdeb:446c:912d:8da::/64,fdeb:446c:912d:8da::1" - ipv6.addr-gen-mode: "default" - ipv6.ip6-privacy: "-1" - dummy._: "" - proxy._: "" + connection: + interface-name: "dummy123" + proxy: {{}} + ipv4: + method: "disabled" + ipv6: + addr-gen-mode: "default" + method: "manual" + address1: "fdeb:446c:912d:8da::/64,fdeb:446c:912d:8da::1" + ip6-privacy: "-1" + dummy: {{}} '''.format(UUID, UUID)})