diff --git a/src/abi_compat.c b/src/abi_compat.c index 72de4a6b6..dea044a34 100644 --- a/src/abi_compat.c +++ b/src/abi_compat.c @@ -30,6 +30,11 @@ #include "nm.h" #include "openvswitch.h" +#include +#include +#include +#include + /* These arrays are not useful per-say, but allow us to export the various * struct offsets of the netplan_state members to the linker, which can use * them in a linker script to create symbols pointing to the internal data @@ -70,3 +75,109 @@ netplan_clear_netdefs() netplan_state_reset(&global_state); return n; } + +NETPLAN_INTERNAL void +write_network_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) +{ + GError* error = NULL; + if (!netplan_netdef_write_network_file(&global_state, def, rootdir, path, NULL, &error)) + { + g_fprintf(stderr, "%s", error->message); + exit(1); + } +} + +/** + * Generate networkd configuration in @rootdir/run/systemd/network/ from the + * parsed #netdefs. + * @rootdir: If not %NULL, generate configuration in this root directory + * (useful for testing). + * Returns: TRUE if @def applies to networkd, FALSE otherwise. + */ +gboolean +write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir) +{ + GError* error = NULL; + gboolean has_been_written; + if (!netplan_netdef_write_networkd(&global_state, def, rootdir, &has_been_written, &error)) + { + g_fprintf(stderr, "%s", error->message); + exit(1); + } + return has_been_written; +} + +NETPLAN_INTERNAL void +cleanup_networkd_conf(const char* rootdir) +{ + netplan_networkd_cleanup(rootdir); +} + +// There only for compatibility purposes, the proper implementation is now directly +// in the `generate` binary. +// LCOV_EXCL_START +NETPLAN_ABI void +enable_networkd(const char* generator_dir) +{ + g_autofree char* link = g_build_path(G_DIR_SEPARATOR_S, generator_dir, "multi-user.target.wants", "systemd-networkd.service", NULL); + g_debug("We created networkd configuration, adding %s enablement symlink", link); + safe_mkdir_p_dir(link); + if (symlink("../systemd-networkd.service", link) < 0 && errno != EEXIST) { + g_fprintf(stderr, "failed to create enablement symlink: %m\n"); + exit(1); + } + + g_autofree char* link2 = g_build_path(G_DIR_SEPARATOR_S, generator_dir, "network-online.target.wants", "systemd-networkd-wait-online.service", NULL); + safe_mkdir_p_dir(link2); + if (symlink("/lib/systemd/system/systemd-networkd-wait-online.service", link2) < 0 && errno != EEXIST) { + g_fprintf(stderr, "failed to create enablement symlink: %m\n"); + exit(1); + } +} +// LCOV_EXCL_STOP + +NETPLAN_INTERNAL void +write_nm_conf(NetplanNetDefinition* def, const char* rootdir) +{ + GError* error = NULL; + if (!netplan_netdef_write_nm(&global_state, def, rootdir, NULL, &error)) { + g_fprintf(stderr, "%s", error->message); + exit(1); + } +} + +NETPLAN_INTERNAL void +write_nm_conf_finish(const char* rootdir) +{ + /* Original implementation had no error possible!! */ + g_assert(netplan_state_finish_nm_write(&global_state, rootdir, NULL)); +} + +NETPLAN_INTERNAL void +cleanup_nm_conf(const char* rootdir) +{ + netplan_nm_cleanup(rootdir); +} + +NETPLAN_INTERNAL void +write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) +{ + GError* error = NULL; + if (!netplan_netdef_write_ovs(&global_state, def, rootdir, NULL, &error)) { + g_fprintf(stderr, "%s", error->message); + exit(1); + } +} + +NETPLAN_INTERNAL void +write_ovs_conf_finish(const char* rootdir) +{ + /* Original implementation had no error possible!! */ + g_assert(netplan_state_finish_ovs_write(&global_state, rootdir, NULL)); +} + +NETPLAN_INTERNAL void +cleanup_ovs_conf(const char* rootdir) +{ + netplan_ovs_cleanup(rootdir); +} diff --git a/src/generate.c b/src/generate.c index 00c7035a1..388d86a56 100644 --- a/src/generate.c +++ b/src/generate.c @@ -56,6 +56,32 @@ reload_udevd(void) g_spawn_sync(NULL, (gchar**)argv, NULL, G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, NULL, NULL, NULL, NULL); }; +/** + * Create enablement symlink for systemd-networkd.service. + */ +static void +enable_networkd(const char* generator_dir) +{ + g_autofree char* link = g_build_path(G_DIR_SEPARATOR_S, generator_dir, "multi-user.target.wants", "systemd-networkd.service", NULL); + g_debug("We created networkd configuration, adding %s enablement symlink", link); + safe_mkdir_p_dir(link); + if (symlink("../systemd-networkd.service", link) < 0 && errno != EEXIST) { + // LCOV_EXCL_START + g_fprintf(stderr, "failed to create enablement symlink: %m\n"); + exit(1); + // LCOV_EXCL_STOP + } + + g_autofree char* link2 = g_build_path(G_DIR_SEPARATOR_S, generator_dir, "network-online.target.wants", "systemd-networkd-wait-online.service", NULL); + safe_mkdir_p_dir(link2); + if (symlink("/lib/systemd/system/systemd-networkd-wait-online.service", link2) < 0 && errno != EEXIST) { + // LCOV_EXCL_START + g_fprintf(stderr, "failed to create enablement symlink: %m\n"); + exit(1); + // LCOV_EXCL_STOP + } +} + // LCOV_EXCL_START /* covered via 'cloud-init' integration test */ static gboolean diff --git a/src/networkd.c b/src/networkd.c index f6c9ec984..4012beb61 100644 --- a/src/networkd.c +++ b/src/networkd.c @@ -46,11 +46,11 @@ wifi_append_freq(gpointer key, gpointer value, gpointer user_data) /** * append wowlan_triggers= string for wpa_supplicant.conf */ -static void -append_wifi_wowlan_flags(NetplanWifiWowlanFlag flag, GString* str) { +static gboolean +append_wifi_wowlan_flags(NetplanWifiWowlanFlag flag, GString* str, GError** error) { if (flag & NETPLAN_WIFI_WOWLAN_TYPES[0].flag || flag >= NETPLAN_WIFI_WOWLAN_TCP) { - g_fprintf(stderr, "ERROR: unsupported wowlan_triggers mask: 0x%x\n", flag); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: unsupported wowlan_triggers mask: 0x%x\n", flag); + return FALSE; } for (unsigned i = 0; NETPLAN_WIFI_WOWLAN_TYPES[i].name != NULL; ++i) { if (flag & NETPLAN_WIFI_WOWLAN_TYPES[i].flag) { @@ -59,6 +59,7 @@ append_wifi_wowlan_flags(NetplanWifiWowlanFlag flag, GString* str) { } /* replace trailing space with newline */ str = g_string_overwrite(str, str->len-1, "\n"); + return TRUE; } /** @@ -525,8 +526,8 @@ write_addr_option(NetplanAddressOptions* o, GString* s) "ERROR: %s: networkd requires that %s has the same value in both " \ "dhcp4_overrides and dhcp6_overrides\n" -static void -combine_dhcp_overrides(const NetplanNetDefinition* def, NetplanDHCPOverrides* combined_dhcp_overrides) +static gboolean +combine_dhcp_overrides(const NetplanNetDefinition* def, NetplanDHCPOverrides* combined_dhcp_overrides, GError** error) { /* if only one of dhcp4 or dhcp6 is enabled, those overrides are used */ if (def->dhcp4 && !def->dhcp6) { @@ -538,51 +539,58 @@ combine_dhcp_overrides(const NetplanNetDefinition* def, NetplanDHCPOverrides* co * we enforce that they are the same. */ if (def->dhcp4_overrides.use_dns != def->dhcp6_overrides.use_dns) { - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "use-dns"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "use-dns"); + return FALSE; } if (g_strcmp0(def->dhcp4_overrides.use_domains, def->dhcp6_overrides.use_domains) != 0){ - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "use-domains"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "use-domains"); + return FALSE; } if (def->dhcp4_overrides.use_ntp != def->dhcp6_overrides.use_ntp) { - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "use-ntp"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "use-ntp"); + return FALSE; } if (def->dhcp4_overrides.send_hostname != def->dhcp6_overrides.send_hostname) { - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "send-hostname"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "send-hostname"); + return FALSE; } if (def->dhcp4_overrides.use_hostname != def->dhcp6_overrides.use_hostname) { - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "use-hostname"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "use-hostname"); + return FALSE; } if (def->dhcp4_overrides.use_mtu != def->dhcp6_overrides.use_mtu) { - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "use-mtu"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "use-mtu"); + return FALSE; } if (g_strcmp0(def->dhcp4_overrides.hostname, def->dhcp6_overrides.hostname) != 0) { - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "hostname"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "hostname"); + return FALSE; } if (def->dhcp4_overrides.metric != def->dhcp6_overrides.metric) { - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "route-metric"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "route-metric"); + return FALSE; } if (def->dhcp4_overrides.use_routes != def->dhcp6_overrides.use_routes) { - g_fprintf(stderr, DHCP_OVERRIDES_ERROR, def->id, "use-routes"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, DHCP_OVERRIDES_ERROR, def->id, "use-routes"); + return FALSE; } /* Just use dhcp4_overrides now, since we know they are the same. */ *combined_dhcp_overrides = def->dhcp4_overrides; } + return TRUE; } /** * Write the needed networkd .network configuration for the selected netplan definition. */ -void -write_network_file(const NetplanNetDefinition* def, const char* rootdir, const char* path) +gboolean +netplan_netdef_write_network_file( + const NetplanState* np_state, + const NetplanNetDefinition* def, + const char *rootdir, + const char* path, + gboolean* has_been_written, + GError** error) { GString* network = NULL; GString* link = NULL; @@ -590,9 +598,11 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c mode_t orig_umask; gboolean is_optional = def->optional; + SET_OPT_OUT_PTR(has_been_written, 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; + return TRUE; } /* Prepare the [Link] section of the .network file. */ @@ -668,8 +678,8 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c /* EUI-64 mode is enabled by default, if no IPv6Token= is specified */ /* TODO: Enable stable-privacy mode for networkd, once PR#16618 has been released: * https://github.com/systemd/systemd/pull/16618 */ - g_fprintf(stderr, "ERROR: %s: ipv6-address-generation mode is not supported by networkd\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: ipv6-address-generation mode is not supported by networkd\n", def->id); + return FALSE; } if (def->accept_ra == NETPLAN_RA_MODE_ENABLED) g_string_append_printf(network, "IPv6AcceptRA=yes\n"); @@ -720,7 +730,7 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c if (def->has_vlans && def->backend != NETPLAN_BACKEND_OVS) { /* iterate over all netdefs to find VLANs attached to us */ - GList *l = netdefs_ordered; + GList *l = np_state->netdefs_ordered; const NetplanNetDefinition* nd; for (; l != NULL; l = l->next) { nd = l->data; @@ -762,7 +772,8 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c g_string_append_printf(network, "ClientIdentifier=%s\n", def->dhcp_identifier); NetplanDHCPOverrides combined_dhcp_overrides; - combine_dhcp_overrides(def, &combined_dhcp_overrides); + if (!combine_dhcp_overrides(def, &combined_dhcp_overrides, error)) + return FALSE; if (combined_dhcp_overrides.metric == NETPLAN_METRIC_UNSPEC) { g_string_append_printf(network, "RouteMetric=%i\n", (def->type == NETPLAN_DEF_TYPE_WIFI ? 600 : 100)); @@ -815,6 +826,9 @@ write_network_file(const NetplanNetDefinition* def, const char* rootdir, const c g_string_free_to_file(s, rootdir, path, ".network"); umask(orig_umask); } + + SET_OPT_OUT_PTR(has_been_written, TRUE); + return TRUE; } static void @@ -861,8 +875,8 @@ write_rules_file(const NetplanNetDefinition* def, const char* rootdir) umask(orig_umask); } -static void -append_wpa_auth_conf(GString* s, const NetplanAuthenticationSettings* auth, const char* id) +static gboolean +append_wpa_auth_conf(GString* s, const NetplanAuthenticationSettings* auth, const char* id, GError** error) { switch (auth->key_management) { case NETPLAN_AUTH_KEY_MANAGEMENT_NONE: @@ -916,16 +930,16 @@ append_wpa_auth_conf(GString* s, const NetplanAuthenticationSettings* auth, cons /* must be a hex-digit key representation */ for (unsigned i = 0; i < 64; ++i) if (!isxdigit(auth->password[i])) { - g_fprintf(stderr, "ERROR: %s: PSK length of 64 is only supported for hex-digit representation\n", id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: PSK length of 64 is only supported for hex-digit representation\n", id); + return FALSE; } /* this is required to be unquoted */ g_string_append_printf(s, " psk=%s\n", auth->password); } else if (len < 8 || len > 63) { /* per wpa_supplicant spec, passphrase needs to be between 8 and 63 characters */ - g_fprintf(stderr, "ERROR: %s: ASCII passphrase must be between 8 and 63 characters (inclusive)\n", id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: ASCII passphrase must be between 8 and 63 characters (inclusive)\n", id); + return FALSE; } else { g_string_append_printf(s, " psk=\"%s\"\n", auth->password); } @@ -952,7 +966,7 @@ append_wpa_auth_conf(GString* s, const NetplanAuthenticationSettings* auth, cons if (auth->phase2_auth) { g_string_append_printf(s, " phase2=\"auth=%s\"\n", auth->phase2_auth); } - + return TRUE; } /* netplan-feature: generated-supplicant */ @@ -979,8 +993,8 @@ write_wpa_unit(const NetplanNetDefinition* def, const char* rootdir) g_string_free_to_file(s, rootdir, path, NULL); } -static void -write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir) +static gboolean +write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir, GError** error) { GHashTableIter iter; GString* s = g_string_new("ctrl_interface=/run/wpa_supplicant\n\n"); @@ -991,7 +1005,8 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir) if (def->type == NETPLAN_DEF_TYPE_WIFI) { if (def->wowlan && def->wowlan > NETPLAN_WIFI_WOWLAN_DEFAULT) { g_string_append(s, "wowlan_triggers="); - append_wifi_wowlan_flags(def->wowlan, s); + if (!append_wifi_wowlan_flags(def->wowlan, s, error)) + return FALSE; } NetplanWifiAccessPoint* ap; g_hash_table_iter_init(&iter, def->access_points); @@ -1036,13 +1051,14 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir) g_string_append(s, " mode=1\n"); break; default: - g_fprintf(stderr, "ERROR: %s: %s: networkd does not support this wifi mode\n", def->id, ap->ssid); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: %s: networkd does not support this wifi mode\n", def->id, ap->ssid); + return FALSE; } /* wifi auth trumps netdef auth */ if (ap->has_auth) { - append_wpa_auth_conf(s, &ap->auth, ap->ssid); + if (!append_wpa_auth_conf(s, &ap->auth, ap->ssid, error)) + return FALSE; } else { g_string_append(s, " key_mgmt=NONE\n"); @@ -1053,7 +1069,8 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir) else { /* wired 802.1x auth or similar */ g_string_append(s, "network={\n"); - append_wpa_auth_conf(s, &def->auth, def->id); + if (!append_wpa_auth_conf(s, &def->auth, def->id, error)) + return FALSE; g_string_append(s, "}\n"); } @@ -1061,6 +1078,7 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir) orig_umask = umask(077); g_string_free_to_file(s, rootdir, path, NULL); umask(orig_umask); + return TRUE; } /** @@ -1068,12 +1086,19 @@ write_wpa_conf(const NetplanNetDefinition* def, const char* rootdir) * parsed #netdefs. * @rootdir: If not %NULL, generate configuration in this root directory * (useful for testing). - * Returns: TRUE if @def applies to networkd, FALSE otherwise. + * @has_been_written: TRUE if @def applies to networkd, FALSE otherwise. + * Returns: FALSE on error. */ -gboolean -write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir) +NETPLAN_INTERNAL gboolean +netplan_netdef_write_networkd( + const NetplanState* np_state, + const NetplanNetDefinition* def, + const char *rootdir, + gboolean* has_been_written, + GError** error) { g_autofree char* path_base = g_strjoin(NULL, "run/systemd/network/10-netplan-", def->id, NULL); + SET_OPT_OUT_PTR(has_been_written, FALSE); /* We want this for all backends when renaming, as *.link and *.rules files are * evaluated by udev, not networkd itself or NetworkManager. */ @@ -1082,24 +1107,25 @@ write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir) if (def->backend != NETPLAN_BACKEND_NETWORKD) { g_debug("networkd: definition %s is not for us (backend %i)", def->id, def->backend); - return FALSE; + return TRUE; } if (def->type == NETPLAN_DEF_TYPE_MODEM) { - g_fprintf(stderr, "ERROR: %s: networkd backend does not support GSM/CDMA modem configuration\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: networkd backend does not support GSM/CDMA modem configuration\n", def->id); + return FALSE; } if (def->type == NETPLAN_DEF_TYPE_WIFI || def->has_auth) { g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa-", def->id, ".service", NULL); g_autofree char* slink = g_strjoin(NULL, "/run/systemd/system/netplan-wpa-", def->id, ".service", NULL); if (def->type == NETPLAN_DEF_TYPE_WIFI && def->has_match) { - g_fprintf(stderr, "ERROR: %s: networkd backend does not support wifi with match:, only by interface name\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: networkd backend does not support wifi with match:, only by interface name\n", def->id); + return FALSE; } g_debug("Creating wpa_supplicant config"); - write_wpa_conf(def, rootdir); + if (!write_wpa_conf(def, rootdir, error)) + return FALSE; g_debug("Creating wpa_supplicant unit %s", slink); write_wpa_unit(def, rootdir); @@ -1109,8 +1135,8 @@ write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir) if (symlink(slink, link) < 0 && errno != EEXIST) { // LCOV_EXCL_START - g_fprintf(stderr, "failed to create enablement symlink: %m\n"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "failed to create enablement symlink: %m\n"); + return FALSE; // LCOV_EXCL_STOP } @@ -1119,6 +1145,7 @@ write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir) if (def->type >= NETPLAN_DEF_TYPE_VIRTUAL) write_netdev_file(def, rootdir, path_base); write_network_file(def, rootdir, path_base); + SET_OPT_OUT_PTR(has_been_written, TRUE); return TRUE; } @@ -1126,7 +1153,7 @@ write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir) * Clean up all generated configurations in @rootdir from previous runs. */ void -cleanup_networkd_conf(const char* rootdir) +netplan_networkd_cleanup(const char* rootdir) { unlink_glob(rootdir, "/run/systemd/network/10-netplan-*"); unlink_glob(rootdir, "/run/netplan/wpa-*.conf"); @@ -1137,29 +1164,3 @@ cleanup_networkd_conf(const char* rootdir) * upgraded system, we need to make sure to clean those up. */ unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-wpa@*.service"); } - -/** - * Create enablement symlink for systemd-networkd.service. - */ -void -enable_networkd(const char* generator_dir) -{ - g_autofree char* link = g_build_path(G_DIR_SEPARATOR_S, generator_dir, "multi-user.target.wants", "systemd-networkd.service", NULL); - g_debug("We created networkd configuration, adding %s enablement symlink", link); - safe_mkdir_p_dir(link); - if (symlink("../systemd-networkd.service", link) < 0 && errno != EEXIST) { - // LCOV_EXCL_START - g_fprintf(stderr, "failed to create enablement symlink: %m\n"); - exit(1); - // LCOV_EXCL_STOP - } - - g_autofree char* link2 = g_build_path(G_DIR_SEPARATOR_S, generator_dir, "network-online.target.wants", "systemd-networkd-wait-online.service", NULL); - safe_mkdir_p_dir(link2); - if (symlink("/lib/systemd/system/systemd-networkd-wait-online.service", link2) < 0 && errno != EEXIST) { - // LCOV_EXCL_START - g_fprintf(stderr, "failed to create enablement symlink: %m\n"); - exit(1); - // LCOV_EXCL_STOP - } -} diff --git a/src/networkd.h b/src/networkd.h index be2bb291e..0615f5979 100644 --- a/src/networkd.h +++ b/src/networkd.h @@ -21,13 +21,29 @@ #include NETPLAN_INTERNAL gboolean -write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir); +netplan_netdef_write_networkd( + const NetplanState* np_state, + const NetplanNetDefinition* def, + const char *rootdir, + gboolean* has_been_written, + GError** error); -NETPLAN_INTERNAL void -write_network_file(const NetplanNetDefinition* def, const char* rootdir, const char* path); +NETPLAN_INTERNAL gboolean +netplan_netdef_write_network_file( + const NetplanState* np_state, + const NetplanNetDefinition* def, + const char *rootdir, + const char* path, + gboolean* has_been_written, + GError** error); NETPLAN_INTERNAL void -cleanup_networkd_conf(const char* rootdir); +netplan_networkd_cleanup(const char* rootdir); +/* Deprecated API */ +NETPLAN_INTERNAL gboolean +write_networkd_conf(const NetplanNetDefinition* def, const char* rootdir); NETPLAN_INTERNAL void -enable_networkd(const char* generator_dir); +write_network_file(const NetplanNetDefinition* def, const char* rootdir, const char* path); +NETPLAN_INTERNAL void +cleanup_networkd_conf(const char* rootdir); diff --git a/src/nm.c b/src/nm.c index 214bcbd27..ddea8c44e 100644 --- a/src/nm.c +++ b/src/nm.c @@ -203,8 +203,8 @@ write_search_domains(const NetplanNetDefinition* def, const char* group, GKeyFil } } -static void -write_routes(const NetplanNetDefinition* def, GKeyFile *kf, int family) +static gboolean +write_routes(const NetplanNetDefinition* def, GKeyFile *kf, int family, GError** error) { const gchar* group = NULL; gchar* tmp_key = NULL; @@ -230,8 +230,8 @@ write_routes(const NetplanNetDefinition* def, GKeyFile *kf, int family) destination = cur_route->to; if (cur_route->type && g_ascii_strcasecmp(cur_route->type, "unicast") != 0) { - g_fprintf(stderr, "ERROR: %s: NetworkManager only supports unicast routes\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: NetworkManager only supports unicast routes\n", def->id); + return FALSE; } /* For IPv6 addresses, kernel and NetworkManager don't support a scope. @@ -280,6 +280,7 @@ write_routes(const NetplanNetDefinition* def, GKeyFile *kf, int family) j++; } } + return TRUE; } static void @@ -358,8 +359,8 @@ write_bridge_params(const NetplanNetDefinition* def, GKeyFile *kf) } } -static void -write_wireguard_params(const NetplanNetDefinition* def, GKeyFile *kf) +static gboolean +write_wireguard_params(const NetplanNetDefinition* def, GKeyFile *kf, GError** error) { gchar* tmp_group = NULL; g_assert(def->tunnel.private_key); @@ -369,8 +370,8 @@ write_wireguard_params(const NetplanNetDefinition* def, GKeyFile *kf) * string could (theoretically) start with '/', so we use is_wireguard_key() * as well to check for more specific characteristics (if needed). */ if (def->tunnel.private_key[0] == '/' && !is_wireguard_key(def->tunnel.private_key)) { - g_fprintf(stderr, "%s: private key needs to be base64 encoded when using the NM backend\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "%s: private key needs to be base64 encoded when using the NM backend\n", def->id); + return FALSE; } else g_key_file_set_string(kf, "wireguard", "private-key", def->tunnel.private_key); @@ -395,8 +396,8 @@ write_wireguard_params(const NetplanNetDefinition* def, GKeyFile *kf) * as well to check for more specific characteristics (if needed). */ if (peer->preshared_key) { if (peer->preshared_key[0] == '/' && !is_wireguard_key(peer->preshared_key)) { - g_fprintf(stderr, "%s: shared key needs to be base64 encoded when using the NM backend\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "%s: shared key needs to be base64 encoded when using the NM backend\n", def->id); + return FALSE; } else { 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); @@ -410,6 +411,7 @@ write_wireguard_params(const NetplanNetDefinition* def, GKeyFile *kf) } g_free(tmp_group); } + return TRUE; } static void @@ -488,10 +490,10 @@ write_wifi_auth_parameters(const NetplanAuthenticationSettings* auth, GKeyFile * } static void -maybe_generate_uuid(NetplanNetDefinition* def) +maybe_generate_uuid(const NetplanNetDefinition* def) { if (uuid_is_null(def->uuid)) - uuid_generate(def->uuid); + uuid_generate((unsigned char*)def->uuid); } /** @@ -550,11 +552,10 @@ write_fallback_key_value(GQuark key_id, gpointer value, gpointer user_data) * @ap: The access point for which to create a connection. Must be %NULL for * non-wifi types. */ -static void -write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const NetplanWifiAccessPoint* ap) +static gboolean +write_nm_conf_access_point(const NetplanNetDefinition* def, const char* rootdir, const NetplanWifiAccessPoint* ap, GError** error) { 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; @@ -571,7 +572,7 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const 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; + return TRUE; } kf = g_key_file_new(); @@ -609,8 +610,8 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const /* XXX: For now NetworkManager only supports the "manual" activation * mode */ if (!!g_strcmp0(def->activation_mode, "manual")) { - g_fprintf(stderr, "ERROR: %s: NetworkManager definitions do not support activation-mode %s\n", def->id, def->activation_mode); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: NetworkManager definitions do not support activation-mode %s\n", def->id, def->activation_mode); + return FALSE; } /* "manual" */ g_key_file_set_boolean(kf, "connection", "autoconnect", FALSE); @@ -687,8 +688,8 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const } if (def->ipv6_mtubytes) { - g_fprintf(stderr, "ERROR: %s: NetworkManager definitions do not support ipv6-mtu\n", def->id); - exit(1); + 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) { @@ -743,9 +744,10 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const write_bond_parameters(def, kf); if (def->type == NETPLAN_DEF_TYPE_TUNNEL) { - if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) - write_wireguard_params(def, kf); - else + if (def->tunnel.mode == NETPLAN_TUNNEL_MODE_WIREGUARD) { + if (!write_wireguard_params(def, kf, error)) + return FALSE; + } else write_tunnel_params(def, kf); } @@ -787,7 +789,8 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const /* We can only write search domains and routes if we have an address */ if (def->ip4_addresses || def->dhcp4) { write_search_domains(def, "ipv4", kf); - write_routes(def, kf, AF_INET); + if (!write_routes(def, kf, AF_INET, error)) + return FALSE; } if (!def->dhcp4_overrides.use_routes) { @@ -829,7 +832,8 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const write_search_domains(def, "ipv6", kf); /* We can only write valid routes if there is a DHCPv6 or static IPv6 address */ - write_routes(def, kf, AF_INET6); + if (!write_routes(def, kf, AF_INET6, error)) + return FALSE; if (!def->dhcp6_overrides.use_routes) { g_key_file_set_boolean(kf, "ipv6", "ignore-auto-routes", TRUE); @@ -846,7 +850,7 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const 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); + g_datalist_foreach((GData**)&def->backend_settings.nm.passthrough, write_fallback_key_value, kf); } if (ap) { @@ -895,13 +899,10 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const full_path = g_strjoin(G_DIR_SEPARATOR_S, rootdir ?: "", conf_path, NULL); orig_umask = umask(077); 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 - } + if (!g_key_file_save_to_file(kf, full_path, error)) + return FALSE; // LCOV_EXCL_LINE umask(orig_umask); + return TRUE; } /** @@ -910,36 +911,46 @@ write_nm_conf_access_point(NetplanNetDefinition* def, const char* rootdir, const * @rootdir: If not %NULL, generate configuration in this root directory * (useful for testing). */ -void -write_nm_conf(NetplanNetDefinition* def, const char* rootdir) +gboolean +netplan_netdef_write_nm( + const NetplanState* np_state, + const NetplanNetDefinition* netdef, + const char* rootdir, + gboolean* has_been_written, + GError** error) { - if (def->backend != NETPLAN_BACKEND_NM) { - g_debug("NetworkManager: definition %s is not for us (backend %i)", def->id, def->backend); - return; + gboolean no_error = TRUE; + + SET_OPT_OUT_PTR(has_been_written, FALSE); + if (netdef->backend != NETPLAN_BACKEND_NM) { + g_debug("NetworkManager: definition %s is not for us (backend %i)", netdef->id, netdef->backend); + return TRUE; } - if (def->match.driver && !def->set_name) { - g_fprintf(stderr, "ERROR: %s: NetworkManager definitions do not support matching by driver\n", def->id); - exit(1); + if (netdef->match.driver && !netdef->set_name) { + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: NetworkManager definitions do not support matching by driver\n", netdef->id); + return FALSE; } - if (def->address_options) { - g_fprintf(stderr, "ERROR: %s: NetworkManager does not support address options\n", def->id); - exit(1); + if (netdef->address_options) { + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: %s: NetworkManager does not support address options\n", netdef->id); + return FALSE; } - if (def->type == NETPLAN_DEF_TYPE_WIFI) { + if (netdef->type == NETPLAN_DEF_TYPE_WIFI) { GHashTableIter iter; gpointer key; const NetplanWifiAccessPoint* ap; - g_assert(def->access_points); - g_hash_table_iter_init(&iter, def->access_points); - while (g_hash_table_iter_next(&iter, &key, (gpointer) &ap)) - write_nm_conf_access_point(def, rootdir, ap); + g_assert(netdef->access_points); + g_hash_table_iter_init(&iter, netdef->access_points); + while (g_hash_table_iter_next(&iter, &key, (gpointer) &ap) && no_error) + no_error = write_nm_conf_access_point(netdef, rootdir, ap, error); } else { - g_assert(def->access_points == NULL); - write_nm_conf_access_point(def, rootdir, NULL); + g_assert(netdef->access_points == NULL); + no_error = write_nm_conf_access_point(netdef, rootdir, NULL, error); } + SET_OPT_OUT_PTR(has_been_written, TRUE); + return no_error; } static void @@ -960,20 +971,23 @@ nd_append_non_nm_ids(gpointer data, gpointer str) } } -void -write_nm_conf_finish(const char* rootdir) +gboolean +netplan_state_finish_nm_write( + const NetplanState* np_state, + const char* rootdir, + GError** error) { GString *s = NULL; gsize len; - if (!netdefs || g_hash_table_size(netdefs) == 0) - return; + 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(netdefs_ordered, nd_append_non_nm_ids, s); + 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); else @@ -982,17 +996,19 @@ write_nm_conf_finish(const char* rootdir) /* write generated udev rules */ if (udev_rules) g_string_free_to_file(udev_rules, rootdir, "run/udev/rules.d/90-netplan.rules", NULL); + return TRUE; } /** * Clean up all generated configurations in @rootdir from previous runs. */ -void -cleanup_nm_conf(const char* rootdir) +gboolean +netplan_nm_cleanup(const char* rootdir) { g_autofree char* confpath = g_strjoin(NULL, rootdir ?: "", "/run/NetworkManager/conf.d/netplan.conf", NULL); g_autofree char* global_manage_path = g_strjoin(NULL, rootdir ?: "", "/run/NetworkManager/conf.d/10-globally-managed-devices.conf", NULL); unlink(confpath); unlink(global_manage_path); unlink_glob(rootdir, "/run/NetworkManager/system-connections/netplan-*"); + return TRUE; } diff --git a/src/nm.h b/src/nm.h index ac86f00ac..1c42f275a 100644 --- a/src/nm.h +++ b/src/nm.h @@ -19,11 +19,27 @@ #include "netplan.h" +NETPLAN_INTERNAL gboolean +netplan_netdef_write_nm( + const NetplanState* np_state, + const NetplanNetDefinition* netdef, + const char* rootdir, + gboolean* has_been_written, + GError** error); + +NETPLAN_INTERNAL gboolean +netplan_state_finish_nm_write( + const NetplanState* np_state, + const char* rootdir, + GError** error); + +NETPLAN_INTERNAL gboolean +netplan_nm_cleanup(const char* rootdir); + +/* Deprecated API */ NETPLAN_INTERNAL void write_nm_conf(NetplanNetDefinition* def, const char* rootdir); - NETPLAN_INTERNAL void write_nm_conf_finish(const char* rootdir); - NETPLAN_INTERNAL void cleanup_nm_conf(const char* rootdir); diff --git a/src/openvswitch.c b/src/openvswitch.c index 3bf8631a3..0a9225a51 100644 --- a/src/openvswitch.c +++ b/src/openvswitch.c @@ -29,8 +29,8 @@ #include "util.h" #include "util-internal.h" -static void -write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, gboolean physical, gboolean cleanup, const char* dependency) +static gboolean +write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, gboolean physical, gboolean cleanup, const char* dependency, GError** error) { g_autofree gchar* id_escaped = NULL; g_autofree char* link = g_strjoin(NULL, rootdir ?: "", "/run/systemd/system/systemd-networkd.service.wants/netplan-ovs-", id, ".service", NULL); @@ -67,10 +67,11 @@ write_ovs_systemd_unit(const char* id, const GString* cmds, const char* rootdir, safe_mkdir_p_dir(link); if (symlink(path, link) < 0 && errno != EEXIST) { // LCOV_EXCL_START - g_fprintf(stderr, "failed to create enablement symlink: %m\n"); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "failed to create enablement symlink: %m\n"); + return FALSE; // LCOV_EXCL_STOP } + return TRUE; } #define append_systemd_cmd(s, command, ...) \ @@ -160,7 +161,7 @@ setup_patch_port(GString* s, const NetplanNetDefinition* def) } static char* -write_ovs_bond_interfaces(const NetplanNetDefinition* def, GString* cmds) +write_ovs_bond_interfaces(const NetplanState* np_state, const NetplanNetDefinition* def, GString* cmds, GError** error) { NetplanNetDefinition* tmp_nd; GHashTableIter iter; @@ -170,14 +171,14 @@ write_ovs_bond_interfaces(const NetplanNetDefinition* def, GString* cmds) GString* patch_ports = g_string_new(""); if (!def->bridge) { - g_fprintf(stderr, "Bond %s needs to be a slave of an OpenVSwitch bridge\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Bond %s needs to be a slave of an OpenVSwitch bridge\n", def->id); + return NULL; } s = g_string_new(OPENVSWITCH_OVS_VSCTL " --may-exist add-bond"); g_string_append_printf(s, " %s %s", def->bridge, def->id); - g_hash_table_iter_init(&iter, netdefs); + g_hash_table_iter_init(&iter, np_state->netdefs); while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &tmp_nd)) { if (!g_strcmp0(def->id, tmp_nd->bond)) { /* Append and count bond interfaces */ @@ -188,8 +189,8 @@ write_ovs_bond_interfaces(const NetplanNetDefinition* def, GString* cmds) } } if (i < 2) { - g_fprintf(stderr, "Bond %s needs to have at least 2 slave interfaces\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "Bond %s needs to have at least 2 slave interfaces\n", def->id); + return NULL; } g_string_append(s, patch_ports->str); @@ -207,8 +208,8 @@ write_ovs_tag_netplan(const gchar* id, const char* type, GString* cmds) type, id); } -static void -write_ovs_bond_mode(const NetplanNetDefinition* def, GString* cmds) +static gboolean +write_ovs_bond_mode(const NetplanNetDefinition* def, GString* cmds, GError** error) { char* value = NULL; /* OVS supports only "active-backup", "balance-tcp" and "balance-slb": @@ -220,14 +221,15 @@ write_ovs_bond_mode(const NetplanNetDefinition* def, GString* cmds) append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Port %s bond_mode=%s", def->id, value); write_ovs_tag_setting(def->id, "Port", "bond_mode", NULL, value, cmds); } else { - g_fprintf(stderr, "%s: bond mode '%s' not supported by openvswitch\n", + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "%s: bond mode '%s' not supported by openvswitch\n", def->id, def->bond_params.mode); - exit(1); + return FALSE; } + return TRUE; } static void -write_ovs_bridge_interfaces(const NetplanNetDefinition* def, GString* cmds) +write_ovs_bridge_interfaces(const NetplanState* np_state, const NetplanNetDefinition* def, GString* cmds) { NetplanNetDefinition* tmp_nd; GHashTableIter iter; @@ -235,7 +237,7 @@ write_ovs_bridge_interfaces(const NetplanNetDefinition* def, GString* cmds) append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " --may-exist add-br %s", def->id); - g_hash_table_iter_init(&iter, netdefs); + g_hash_table_iter_init(&iter, np_state->netdefs); while (g_hash_table_iter_next(&iter, (gpointer) &key, (gpointer) &tmp_nd)) { /* OVS bonds will connect to their OVS bridge and create the interface/port themselves */ if ((tmp_nd->type != NETPLAN_DEF_TYPE_BOND || tmp_nd->backend != NETPLAN_BACKEND_OVS) @@ -265,38 +267,48 @@ write_ovs_protocols(const NetplanOVSSettings* ovs_settings, const gchar* bridge, } static gboolean -check_ovs_ssl(gchar* target) +check_ovs_ssl(const NetplanOVSSettings* settings, gchar* target, gboolean* needs_ssl, GError** error) { /* Check if target needs ssl */ if (g_str_has_prefix(target, "ssl:") || g_str_has_prefix(target, "pssl:")) { - /* Check if SSL is configured in ovs_settings_global.ssl */ - if (!ovs_settings_global.ssl.ca_certificate || !ovs_settings_global.ssl.client_certificate || - !ovs_settings_global.ssl.client_key) { - g_fprintf(stderr, "ERROR: openvswitch bridge controller target '%s' needs SSL configuration, but global 'openvswitch.ssl' settings are not set\n", target); - exit(1); + /* Check if SSL is configured in settings->ssl */ + if (!settings->ssl.ca_certificate || !settings->ssl.client_certificate || + !settings->ssl.client_key) { + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "ERROR: openvswitch bridge controller target '%s' needs SSL configuration, but global 'openvswitch.ssl' settings are not set\n", target); + return FALSE; } + *needs_ssl = TRUE; return TRUE; } - return FALSE; + *needs_ssl = FALSE; + return TRUE; } -static void -write_ovs_bridge_controller_targets(const NetplanOVSController* controller, const gchar* bridge, GString* cmds) +static gboolean +write_ovs_bridge_controller_targets(const NetplanOVSSettings* settings, const NetplanOVSController* controller, const gchar* bridge, GString* cmds, GError** error) { gchar* target = g_array_index(controller->addresses, char*, 0); - gboolean needs_ssl = check_ovs_ssl(target); - GString* s = g_string_new(target); + GString* s = g_string_sized_new(0); + gboolean ret = TRUE; + gboolean needs_ssl = FALSE; - for (unsigned i = 1; i < controller->addresses->len; ++i) { + for (unsigned i = 0; i < controller->addresses->len; ++i) { target = g_array_index(controller->addresses, char*, i); if (!needs_ssl) - needs_ssl = check_ovs_ssl(target); - g_string_append_printf(s, " %s", target); + if (!check_ovs_ssl(settings, target, &needs_ssl, error)) { + ret = FALSE; + goto cleanup; + } + g_string_append_printf(s, "%s ", target); } + g_string_erase(s, s->len-1, 1); append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set-controller %s %s", bridge, s->str); write_ovs_tag_setting(bridge, "Bridge", "global", "set-controller", s->str, cmds); + +cleanup: g_string_free(s, TRUE); + return ret; } /** @@ -304,14 +316,17 @@ write_ovs_bridge_controller_targets(const NetplanOVSController* controller, cons * @rootdir: If not %NULL, generate configuration in this root directory * (useful for testing). */ -void -write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) +gboolean +netplan_netdef_write_ovs(const NetplanState* np_state, const NetplanNetDefinition* def, const char* rootdir, gboolean* has_been_written, GError** error) { GString* cmds = g_string_new(NULL); gchar* dependency = NULL; const char* type = netplan_type_to_table_name(def->type); g_autofree char* base_config_path = NULL; char* value = NULL; + const NetplanOVSSettings* settings = &np_state->ovs_settings; + + SET_OPT_OUT_PTR(has_been_written, FALSE); /* TODO: maybe dynamically query the ovs-vsctl tool path? */ @@ -321,19 +336,20 @@ write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) if (def->backend == NETPLAN_BACKEND_OVS) { switch (def->type) { case NETPLAN_DEF_TYPE_BOND: - dependency = write_ovs_bond_interfaces(def, cmds); + dependency = write_ovs_bond_interfaces(np_state, def, cmds, error); + if (!dependency) + return FALSE; write_ovs_tag_netplan(def->id, type, cmds); /* Set LACP mode, default to "off" */ value = def->ovs_settings.lacp? def->ovs_settings.lacp : "off"; append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Port %s lacp=%s", def->id, value); write_ovs_tag_setting(def->id, type, "lacp", NULL, value, cmds); - if (def->bond_params.mode) { - write_ovs_bond_mode(def, cmds); - } + if (def->bond_params.mode && !write_ovs_bond_mode(def, cmds, error)) + return FALSE; break; case NETPLAN_DEF_TYPE_BRIDGE: - write_ovs_bridge_interfaces(def, cmds); + write_ovs_bridge_interfaces(np_state, def, cmds); write_ovs_tag_netplan(def->id, type, cmds); /* Set fail-mode, default to "standalone" */ value = def->ovs_settings.fail_mode? def->ovs_settings.fail_mode : "standalone"; @@ -348,14 +364,15 @@ write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set Bridge %s rstp_enable=%s", def->id, value); write_ovs_tag_setting(def->id, type, "rstp_enable", NULL, value, cmds); /* Set protocols */ - if (def->ovs_settings.protocols && def->ovs_settings.protocols->len > 0) { + if (def->ovs_settings.protocols && def->ovs_settings.protocols->len > 0) write_ovs_protocols(&(def->ovs_settings), def->id, cmds); - } else if (ovs_settings_global.protocols && ovs_settings_global.protocols->len > 0) { - write_ovs_protocols(&(ovs_settings_global), def->id, cmds); - } + else if (settings->protocols && settings->protocols->len > 0) + write_ovs_protocols(settings, def->id, cmds); /* Set controller target addresses */ if (def->ovs_settings.controller.addresses && def->ovs_settings.controller.addresses->len > 0) { - write_ovs_bridge_controller_targets(&(def->ovs_settings.controller), def->id, cmds); + if (!write_ovs_bridge_controller_targets(settings, &(def->ovs_settings.controller), def->id, cmds, error)) + return FALSE; + /* Set controller connection mode, only applicable if at least one controller target address was set */ if (def->ovs_settings.controller.connection_mode) { value = def->ovs_settings.controller.connection_mode; @@ -369,8 +386,8 @@ write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) g_assert(def->peer); dependency = def->bridge?: def->bond; if (!dependency) { - g_fprintf(stderr, "%s: OpenVSwitch patch port needs to be assigned to a bridge/bond\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "%s: OpenVSwitch patch port needs to be assigned to a bridge/bond\n", def->id); + return FALSE; } /* There is no OVS Port which we could tag netplan=true if this * patch port is assigned as an OVS bond interface. Tag the @@ -390,9 +407,8 @@ write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) break; default: - g_fprintf(stderr, "%s: This device type is not supported with the OpenVSwitch backend\n", def->id); - exit(1); - break; + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "%s: This device type is not supported with the OpenVSwitch backend\n", def->id); + return FALSE; } /* Try writing out a base config */ @@ -404,12 +420,13 @@ write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) || (def->ovs_settings.other_config && g_hash_table_size(def->ovs_settings.other_config) > 0)) { dependency = def->bridge?: def->bond; if (!dependency) { - g_fprintf(stderr, "%s: Interface needs to be assigned to an OVS bridge/bond to carry external-ids/other-config\n", def->id); - exit(1); + g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "%s: Interface needs to be assigned to an OVS bridge/bond to carry external-ids/other-config\n", def->id); + return FALSE; } } else { g_debug("openvswitch: definition %s is not for us (backend %i)", def->id, def->backend); - return; + SET_OPT_OUT_PTR(has_been_written, FALSE); + return TRUE; } } @@ -428,59 +445,67 @@ write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir) } /* If we need to configure anything for this netdef, write the required systemd unit */ + gboolean ret = TRUE; if (cmds->len > 0) - write_ovs_systemd_unit(def->id, cmds, rootdir, netplan_type_is_physical(def->type), FALSE, dependency); + ret = write_ovs_systemd_unit(def->id, cmds, rootdir, netplan_type_is_physical(def->type), FALSE, dependency, error); g_string_free(cmds, TRUE); + SET_OPT_OUT_PTR(has_been_written, TRUE); + return ret; } /** * Finalize the OpenVSwitch configuration (global config) */ -void -write_ovs_conf_finish(const char* rootdir) +gboolean +netplan_state_finish_ovs_write(const NetplanState* np_state, const char* rootdir, GError** error) { + const NetplanOVSSettings* settings = &np_state->ovs_settings; GString* cmds = g_string_new(NULL); /* Global external-ids and other-config settings */ - if (ovs_settings_global.external_ids && g_hash_table_size(ovs_settings_global.external_ids) > 0) { - write_ovs_additional_data(ovs_settings_global.external_ids, "open_vswitch", + if (settings->external_ids && g_hash_table_size(settings->external_ids) > 0) + write_ovs_additional_data(settings->external_ids, "open_vswitch", ".", cmds, "external-ids"); - } - if (ovs_settings_global.other_config && g_hash_table_size(ovs_settings_global.other_config) > 0) { - write_ovs_additional_data(ovs_settings_global.other_config, "open_vswitch", + if (settings->other_config && g_hash_table_size(settings->other_config) > 0) + write_ovs_additional_data(settings->other_config, "open_vswitch", ".", cmds, "other-config"); - } - if (ovs_settings_global.ssl.client_key && ovs_settings_global.ssl.client_certificate && - ovs_settings_global.ssl.ca_certificate) { + if (settings->ssl.client_key && settings->ssl.client_certificate && + settings->ssl.ca_certificate) { GString* value = g_string_new(NULL); g_string_printf(value, "%s %s %s", - ovs_settings_global.ssl.client_key, - ovs_settings_global.ssl.client_certificate, - ovs_settings_global.ssl.ca_certificate); + settings->ssl.client_key, + settings->ssl.client_certificate, + settings->ssl.ca_certificate); append_systemd_cmd(cmds, OPENVSWITCH_OVS_VSCTL " set-ssl %s", value->str); write_ovs_tag_setting(".", "open_vswitch", "global", "set-ssl", value->str, cmds); g_string_free(value, TRUE); } + gboolean ret = TRUE; if (cmds->len > 0) - write_ovs_systemd_unit("global", cmds, rootdir, FALSE, FALSE, NULL); + ret = write_ovs_systemd_unit("global", cmds, rootdir, FALSE, FALSE, NULL, error); g_string_free(cmds, TRUE); + if (!ret) + return FALSE; // LCOV_EXCL_LINE /* Clear all netplan=true tagged ports/bonds and bridges, via 'netplan apply --only-ovs-cleanup' */ cmds = g_string_new(NULL); append_systemd_cmd(cmds, SBINDIR "/netplan apply %s", "--only-ovs-cleanup"); - write_ovs_systemd_unit("cleanup", cmds, rootdir, FALSE, TRUE, NULL); + ret = write_ovs_systemd_unit("cleanup", cmds, rootdir, FALSE, TRUE, NULL, error); g_string_free(cmds, TRUE); + return ret; } /** * Clean up all generated configurations in @rootdir from previous runs. */ -void -cleanup_ovs_conf(const char* rootdir) + +gboolean +netplan_ovs_cleanup(const char* rootdir) { unlink_glob(rootdir, "/run/systemd/system/systemd-networkd.service.wants/netplan-ovs-*.service"); unlink_glob(rootdir, "/run/systemd/system/netplan-ovs-*.service"); + return TRUE; } diff --git a/src/openvswitch.h b/src/openvswitch.h index 7abea4df6..ae36fc5b9 100644 --- a/src/openvswitch.h +++ b/src/openvswitch.h @@ -19,11 +19,27 @@ #include "netplan.h" +NETPLAN_INTERNAL gboolean +netplan_netdef_write_ovs( + const NetplanState* np_state, + const NetplanNetDefinition* netdef, + const char* rootdir, + gboolean* has_been_written, + GError** error); + +NETPLAN_INTERNAL gboolean +netplan_state_finish_ovs_write( + const NetplanState* np_state, + const char* rootdir, + GError** error); + +NETPLAN_INTERNAL gboolean +netplan_ovs_cleanup(const char* rootdir); + +/* Deprecated API */ NETPLAN_INTERNAL void write_ovs_conf(const NetplanNetDefinition* def, const char* rootdir); - NETPLAN_INTERNAL void write_ovs_conf_finish(const char* rootdir); - NETPLAN_INTERNAL void cleanup_ovs_conf(const char* rootdir); diff --git a/src/util-internal.h b/src/util-internal.h index ed694451b..a0068ca6a 100644 --- a/src/util-internal.h +++ b/src/util-internal.h @@ -25,6 +25,8 @@ #include #include "netplan.h" +#define SET_OPT_OUT_PTR(ptr,val) { if (ptr) *ptr = val; } + extern GHashTable* wifi_frequency_24; diff --git a/tests/generator/test_auth.py b/tests/generator/test_auth.py index 7d9ff8f03..aa2442b8a 100644 --- a/tests/generator/test_auth.py +++ b/tests/generator/test_auth.py @@ -553,3 +553,14 @@ def test_auth_networkd_wifi_psk_64_non_hexdigit(self): password: "LoremipsumdolorsitametconsecteturadipiscingelitCrastemporvelitnu" dhcp4: yes''', expect_fail=True) self.assertIn("PSK length of 64 is only supported for hex-digit representation", err) + + def test_auth_networkd_wire_psk_64_non_hexdigit(self): + err = self.generate('''network: + version: 2 + ethernets: + eth0: + auth: + key-management: psk + password: "LoremipsumdolorsitametconsecteturadipiscingelitCrastemporvelitnu" + dhcp4: yes''', expect_fail=True) + self.assertIn("PSK length of 64 is only supported for hex-digit representation", err) diff --git a/tests/generator/test_common.py b/tests/generator/test_common.py index f1b21373f..47c1fdc11 100644 --- a/tests/generator/test_common.py +++ b/tests/generator/test_common.py @@ -826,6 +826,12 @@ def test_ip6_addr_gen_token(self): class TestNetworkManager(TestBase): + def test_empty_conf(self): + self.generate('''network: + version: 2 + renderer: NetworkManager''') + self.assert_nm({}) + def test_mtu_all(self): self.generate(textwrap.dedent(""" network: diff --git a/tests/generator/test_routing.py b/tests/generator/test_routing.py index 247f7aed0..89ec7cecc 100644 --- a/tests/generator/test_routing.py +++ b/tests/generator/test_routing.py @@ -1188,9 +1188,9 @@ def test_route_options(self): def test_route_reject_type(self): err = self.generate('''network: version: 2 + renderer: NetworkManager ethernets: engreen: - renderer: NetworkManager addresses: ["192.168.14.2/24"] routes: - to: 10.10.10.0/24 @@ -1202,6 +1202,23 @@ def test_route_reject_type(self): self.assert_nm({}) self.assert_networkd({}) + def test_route_reject_type_v6(self): + err = self.generate('''network: + version: 2 + renderer: NetworkManager + ethernets: + engreen: + addresses: ["2001:f00f::2/128"] + routes: + - to: 2001:dead:beef::2/64 + via: 2001:beef:beef::1 + type: blackhole + ''', expect_fail=True) + self.assertIn('NetworkManager only supports unicast routes', err) + + self.assert_nm({}) + self.assert_networkd({}) + def test_use_routes_v4(self): """[NetworkManager] Validate config when use-routes DHCP4 override is used""" self.generate('''network: