diff options
Diffstat (limited to 'src/nm.c')
-rw-r--r-- | src/nm.c | 296 |
1 files changed, 188 insertions, 108 deletions
@@ -26,58 +26,15 @@ #include <glib/gprintf.h> #include <uuid.h> +#include "names.h" #include "netplan.h" #include "nm.h" #include "parse.h" #include "parse-globals.h" +#include "parse-nm.h" #include "util.h" #include "util-internal.h" #include "validation.h" -#include "parse-nm.h" - -GString* udev_rules; - -/** - * Append NM device specifier of @def to @s. - */ -static void -g_string_append_netdef_match(GString* s, const NetplanNetDefinition* def) -{ - g_assert(!def->match.driver || def->set_name); - if (def->match.mac || def->match.original_name || def->set_name || def->type >= NETPLAN_DEF_TYPE_VIRTUAL) { - if (def->match.mac) { - g_string_append_printf(s, "mac:%s,", def->match.mac); - } - /* MAC could change, e.g. for bond slaves. Ignore by interface-name as well */ - if (def->match.original_name || def->set_name || def->type >= NETPLAN_DEF_TYPE_VIRTUAL) { - /* we always have the renamed name here */ - g_string_append_printf(s, "interface-name:%s,", - (def->type >= NETPLAN_DEF_TYPE_VIRTUAL) ? def->id - : (def->set_name ?: def->match.original_name)); - } - } else { - /* no matches → match all devices of that type */ - switch (def->type) { - case NETPLAN_DEF_TYPE_ETHERNET: - g_string_append(s, "type:ethernet,"); - break; - /* This cannot be reached with just NM and networkd backends, as - * networkd does not support wifi and thus we'll never blacklist a - * wifi device from NM. This would become relevant with another - * wifi-supporting backend, but until then this just spoils 100% - * code coverage. - case NETPLAN_DEF_TYPE_WIFI: - g_string_append(s, "type:wifi"); - break; - */ - - // LCOV_EXCL_START - default: - g_assert_not_reached(); - // LCOV_EXCL_STOP - } - } -} /** * Infer if this is a modem netdef of type GSM. @@ -108,7 +65,11 @@ type_str(const NetplanNetDefinition* def) const NetplanDefType type = def->type; switch (type) { case NETPLAN_DEF_TYPE_ETHERNET: - return "ethernet"; + /* 20-byte IPoIB MAC + colons */ + if (def->ib_mode || (def->match.mac && strlen(def->match.mac) == 59)) + return "infiniband"; + else + return "ethernet"; case NETPLAN_DEF_TYPE_MODEM: if (modem_is_gsm(def)) return "gsm"; @@ -122,13 +83,19 @@ type_str(const NetplanNetDefinition* def) return "bond"; case NETPLAN_DEF_TYPE_VLAN: return "vlan"; + case NETPLAN_DEF_TYPE_VRF: + return "vrf"; case NETPLAN_DEF_TYPE_TUNNEL: if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) return "wireguard"; + else if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN) + return "vxlan"; return "ip-tunnel"; case NETPLAN_DEF_TYPE_NM: /* needs to be overriden by passthrough "connection.type" setting */ - return NULL; + g_assert(def->backend_settings.nm.passthrough); + GData *passthrough = def->backend_settings.nm.passthrough; + return g_datalist_get_data(&passthrough, "connection.type"); // LCOV_EXCL_START default: g_assert_not_reached(); @@ -431,9 +398,6 @@ write_tunnel_params(const NetplanNetDefinition* def, GKeyFile *kf) static void write_dot1x_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile *kf) { - if (auth->eap_method == NETPLAN_AUTH_EAP_NONE) - return; - switch (auth->eap_method) { case NETPLAN_AUTH_EAP_TLS: g_key_file_set_string(kf, "802-1x", "eap", "tls"); @@ -468,14 +432,11 @@ write_dot1x_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile static void write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile *kf) { - if (auth->key_management == NETPLAN_AUTH_KEY_MANAGEMENT_NONE) - return; - switch (auth->key_management) { + case NETPLAN_AUTH_KEY_MANAGEMENT_NONE: + break; case NETPLAN_AUTH_KEY_MANAGEMENT_WPA_PSK: 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_key_file_set_string(kf, "wifi-security", "key-mgmt", "wpa-eap"); @@ -486,7 +447,10 @@ write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile * default: break; // LCOV_EXCL_LINE } - write_dot1x_auth_parameters(auth, kf); + if (auth->eap_method != NETPLAN_AUTH_EAP_NONE) + write_dot1x_auth_parameters(auth, kf); + else if (auth->password) + g_key_file_set_string(kf, "wifi-security", "psk", auth->password); } static void @@ -496,6 +460,60 @@ maybe_generate_uuid(const NetplanNetDefinition* def) uuid_generate((unsigned char*)def->uuid); } +static void +write_vxlan_parameters(const NetplanNetDefinition* def, GKeyFile* kf) +{ + g_assert(def->vxlan); + char uuidstr[37]; + if (def->vxlan->ageing) + g_key_file_set_uint64(kf, "vxlan", "ageing", def->vxlan->ageing); + if (def->tunnel.port) + g_key_file_set_uint64(kf, "vxlan", "destination-port", def->tunnel.port); + if (def->vxlan->vni) + g_key_file_set_uint64(kf, "vxlan", "id", def->vxlan->vni); + if (def->vxlan->mac_learning) + g_key_file_set_boolean(kf, "vxlan", "learning", def->vxlan->mac_learning); + if (def->vxlan->limit) + g_key_file_set_uint64(kf, "vxlan", "limit", def->vxlan->limit); + if (def->tunnel.local_ip) + g_key_file_set_string(kf, "vxlan", "local", def->tunnel.local_ip); + if (def->tunnel.remote_ip) + g_key_file_set_string(kf, "vxlan", "remote", def->tunnel.remote_ip); + if (def->vxlan->arp_proxy) + g_key_file_set_boolean(kf, "vxlan", "proxy", def->vxlan->arp_proxy); + if (def->vxlan->notifications) { + if (def->vxlan->notifications & NETPLAN_VXLAN_NOTIFICATION_L2_MISS) + g_key_file_set_boolean(kf, "vxlan", "l2-miss", TRUE); + if (def->vxlan->notifications & NETPLAN_VXLAN_NOTIFICATION_L3_MISS) + g_key_file_set_boolean(kf, "vxlan", "l3-miss", TRUE); + } + if (def->vxlan->source_port_min && def->vxlan->source_port_max) { + g_key_file_set_uint64(kf, "vxlan", "source-port-min", def->vxlan->source_port_min); + g_key_file_set_uint64(kf, "vxlan", "source-port-max", def->vxlan->source_port_max); + } + if (def->vxlan->tos) + g_key_file_set_uint64(kf, "vxlan", "tos", def->vxlan->tos); + if (def->tunnel_ttl) + g_key_file_set_uint64(kf, "vxlan", "ttl", def->tunnel_ttl); + if (def->vxlan->short_circuit) + g_key_file_set_boolean(kf, "vxlan", "rsc", def->vxlan->short_circuit); + if (def->vxlan->link) { + if (def->vxlan->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->vxlan->link); + uuid_unparse(def->vxlan->link->uuid, uuidstr); + g_key_file_set_string(kf, "vxlan", "parent", uuidstr); + } else { + /* if we have an interface name, use that as parent */ + g_key_file_set_string(kf, "vxlan", "parent", def->vxlan->link->id); + } + } + + if (def->vxlan->checksums || def->vxlan->extensions || def->vxlan->flow_label != G_MAXUINT || def->vxlan->do_not_fragment) + g_warning("%s: checksums/extensions/flow-lable/do-not-fragment are not supported by NetworkManager\n", def->id); +} + /** * Special handling for passthrough mode: read key-value pairs from * "backend_settings.nm.passthrough" and inject them into the keyfile as-is. @@ -595,17 +613,17 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, } nm_type = type_str(def); - if (nm_type) + if (nm_type && def->type != NETPLAN_DEF_TYPE_NM) 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) { + /* VLAN/VXLAN 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_vxlans) && def->has_match) { maybe_generate_uuid(def); uuid_unparse(def->uuid, uuidstr); g_key_file_set_string(kf, "connection", "uuid", uuidstr); @@ -646,6 +664,11 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, if (def->type == NETPLAN_DEF_TYPE_BRIDGE) write_bridge_params(def, kf); + if (def->type == NETPLAN_DEF_TYPE_VRF) { + g_key_file_set_uint64(kf, "vrf", "table", def->vrf_table); + write_routes(def, kf, AF_INET, error); + write_routes(def, kf, AF_INET6, error); + } } if (def->type == NETPLAN_DEF_TYPE_MODEM) { const char* modem_type = modem_is_gsm(def) ? "gsm" : "cdma"; @@ -692,35 +715,31 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, g_key_file_set_string(kf, "connection", "master", def->bond); } + if (def->vrf_link) { + g_key_file_set_string(kf, "connection", "slave-type", "vrf"); + g_key_file_set_string(kf, "connection", "master", def->vrf_link->id); + } + if (def->ipv6_mtubytes) { g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: NetworkManager definitions do not support ipv6-mtu\n", def->id); return FALSE; } if (def->type < NETPLAN_DEF_TYPE_VIRTUAL) { - if (def->type == NETPLAN_DEF_TYPE_ETHERNET) - g_key_file_set_integer(kf, "ethernet", "wake-on-lan", def->wake_on_lan ? 1 : 0); - - 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 (con_type) { + /* Avoid adding an [ethernet] section into the [gsm/cdma] description. */ + if (g_strcmp0(nm_type, "gsm") != 0 || g_strcmp0(nm_type, "cdma") != 0) { + if (g_strcmp0(nm_type, "ethernet") == 0) + g_key_file_set_integer(kf, nm_type, "wake-on-lan", def->wake_on_lan ? 1 : 0); if (!def->set_name && def->match.mac) - g_key_file_set_string(kf, con_type, "mac-address", def->match.mac); + g_key_file_set_string(kf, nm_type, "mac-address", def->match.mac); if (def->set_mac) - g_key_file_set_string(kf, con_type, "cloned-mac-address", def->set_mac); + g_key_file_set_string(kf, nm_type, "cloned-mac-address", def->set_mac); if (def->mtubytes) - g_key_file_set_uint64(kf, con_type, "mtu", def->mtubytes); + g_key_file_set_uint64(kf, nm_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_key_file_set_uint64(kf, nm_type, "wake-on-wlan", def->wowlan); + if (def->ib_mode != NETPLAN_IB_MODE_KERNEL) + g_key_file_set_string(kf, nm_type, "transport-mode", netplan_infiniband_mode_name(def->ib_mode)); } } else { if (def->set_mac) @@ -752,6 +771,8 @@ write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) { if (!write_wireguard_params(def, kf, error)) return FALSE; + } else if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_VXLAN) { + write_vxlan_parameters(def, kf); } else write_tunnel_params(def, kf); } @@ -960,49 +981,108 @@ netplan_netdef_write_nm( return no_error; } -static void -nd_append_non_nm_ids(gpointer data, gpointer str) -{ - const NetplanNetDefinition* nd = data; - - if (nd->backend != NETPLAN_BACKEND_NM) { - if (nd->match.driver) { - /* TODO: NetworkManager supports (non-globbing) "driver:..." matching nowadays */ - /* NM cannot match on drivers, so ignore these via udev rules */ - if (!udev_rules) - udev_rules = g_string_new(NULL); - g_string_append_printf(udev_rules, "ACTION==\"add|change\", SUBSYSTEM==\"net\", ENV{ID_NET_DRIVER}==\"%s\", ENV{NM_UNMANAGED}=\"1\"\n", nd->match.driver); - } else { - g_string_append_netdef_match((GString*) str, nd); - } - } -} - gboolean netplan_state_finish_nm_write( const NetplanState* np_state, const char* rootdir, GError** error) { - GString *s = NULL; - gsize len; + GString* udev_rules = g_string_new(NULL); + GString *nm_conf = g_string_new(NULL); if (netplan_state_get_netdefs_size(np_state) == 0) return TRUE; // LCOV_EXCL_LINE as generate.c already deals with it. /* Set all devices not managed by us to unmanaged, so that NM does not - * auto-connect and interferes */ - s = g_string_new("[keyfile]\n# devices managed by networkd\nunmanaged-devices+="); - len = s->len; - g_list_foreach(np_state->netdefs_ordered, nd_append_non_nm_ids, s); - if (s->len > len) - g_string_free_to_file(s, rootdir, "run/NetworkManager/conf.d/netplan.conf", NULL); + * auto-connect and interferes. + * Also, mark all devices managed by us explicitly, so it won't get in + * conflict with the system's udev rules that might ignore some devices + * in containers via usr/lib/udev/rules.d/85-nm-unmanaged-devices.rules */ + GList* iter = np_state->netdefs_ordered; + while (iter) { + const NetplanNetDefinition* nd = iter->data; + const gchar* nm_type; + GString *tmp = NULL; + guint unmanaged = nd->backend == NETPLAN_BACKEND_NM ? 0 : 1; + + /* 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); + 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), + nd->id, nm_type, !unmanaged); + } + /* Normal case: manage or ignore devices by specific udev rules */ + else { + const gchar *prefix = "SUBSYSTEM==\"net\", ACTION==\"add|change|move\","; + const gchar *suffix = nd->backend == NETPLAN_BACKEND_NM ? " ENV{NM_UNMANAGED}=\"0\"\n" : " ENV{NM_UNMANAGED}=\"1\"\n"; + g_string_append_printf(udev_rules, "# netplan: network.%s.%s (on NetworkManager %s)\n", + netplan_def_type_name(nd->type), nd->id, + unmanaged ? "deny-list" : "allow-list"); + /* Match by explicit interface name, if possible */ + if (nd->set_name) { + // simple case: explicit new interface name + g_string_append_printf(udev_rules, "%s ENV{ID_NET_NAME}==\"%s\",%s", prefix, nd->set_name, suffix); + } else if (!nd->has_match) { + // simple case: explicit netplan ID is interface name + g_string_append_printf(udev_rules, "%s ENV{ID_NET_NAME}==\"%s\",%s", prefix, nd->id, suffix); + } + /* Also, match by explicit (new) MAC, if available */ + if (nd->set_mac) { + tmp = g_string_new(nd->set_mac); + g_string_append_printf(udev_rules, "%s ATTR{address}==\"%s\",%s", prefix, g_string_ascii_down(tmp)->str, suffix); + g_string_free(tmp, TRUE); + } + /* Finally, add a full match, using all rules & globs available + * from the "match" stanza (e.g. original_name/mac/drivers) + * This will match the "old" interface (i.e. original MAC and/or + * interface name) if it got changed */ + if (nd->has_match && (nd->match.original_name || nd->match.mac || nd->match.driver)) { + // match on original name glob + // TODO: maybe support matching on multiple name globs in the future (like drivers) + g_string_append(udev_rules, prefix); + if (nd->match.original_name) + g_string_append_printf(udev_rules, " ENV{ID_NET_NAME}==\"%s\",", nd->match.original_name); + + // match on (explicit) MAC address. Yes this would be unique on its own, but we + // keep it within the "full match" to make the logic more comprehensible. + if (nd->match.mac) { + tmp = g_string_new(nd->match.mac); + g_string_append_printf(udev_rules, " ATTR{address}==\"%s\",", g_string_ascii_down(tmp)->str); + g_string_free(tmp, TRUE); + } + + // match on (multiple) driver globs + if (nd->match.driver) { + gchar *drivers = NULL; + if (strchr(nd->match.driver, '\t')) { + gchar **split = g_strsplit(nd->match.driver, "\t", -1); + drivers = g_strjoinv("|", split); + g_strfreev(split); + } else + drivers = g_strdup(nd->match.driver); + g_string_append_printf(udev_rules, " ENV{ID_NET_DRIVER}==\"%s\",", drivers); + g_free(drivers); + } + g_string_append(udev_rules, suffix); + } + } + iter = iter->next; + } + + /* write generated NetworkManager drop-in config */ + if (nm_conf->len > 0) + g_string_free_to_file(nm_conf, rootdir, "run/NetworkManager/conf.d/netplan.conf", NULL); else - g_string_free(s, TRUE); + g_string_free(nm_conf, TRUE); /* write generated udev rules */ - if (udev_rules) + if (udev_rules->len > 0) g_string_free_to_file(udev_rules, rootdir, "run/udev/rules.d/90-netplan.rules", NULL); + else + g_string_free(udev_rules, TRUE); + return TRUE; } |