From 681dfc70ef98f6ed0c05bcb0fbff00e3fc0799ea Mon Sep 17 00:00:00 2001 From: Michael Biebl Date: Mon, 26 Aug 2024 16:10:34 +0200 Subject: New upstream version 1.48.10 --- src/core/devices/nm-device-bridge.c | 180 ++++++++++++++---- src/core/devices/nm-device-bridge.h | 2 + src/core/devices/nm-device.c | 2 + src/core/devices/nm-lldp-listener.c | 15 +- src/core/nm-policy.c | 275 +++++++++++++++++++++++----- src/libnm-lldp/nm-lldp-neighbor.c | 47 ++--- src/libnm-lldp/nm-lldp-neighbor.h | 4 +- src/libnm-lldp/nm-lldp-rx-internal.h | 2 +- src/libnm-lldp/nm-lldp-rx.c | 2 +- src/libnm-lldp/nm-lldp-rx.h | 2 +- src/libnm-platform/nm-linux-platform.c | 172 ++++++++++++++++- src/libnm-platform/nm-platform-utils.c | 83 +++++++++ src/libnm-platform/nm-platform-utils.h | 7 + src/libnm-platform/nm-platform.c | 49 ++++- src/libnm-platform/nm-platform.h | 33 ++-- src/libnm-platform/nmp-base.h | 9 + src/libnm-platform/tests/test-nm-platform.c | 237 ++++++++++++++++++++++++ src/nmcli/connections.c | 11 +- 18 files changed, 994 insertions(+), 138 deletions(-) (limited to 'src') diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c index b256056d..7a496d96 100644 --- a/src/core/devices/nm-device-bridge.c +++ b/src/core/devices/nm-device-bridge.c @@ -13,6 +13,7 @@ #include "NetworkManagerUtils.h" #include "nm-device-private.h" #include "libnm-platform/nm-platform.h" +#include "libnm-platform/nm-platform-utils.h" #include "nm-device-factory.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-core-intern/nm-core-internal.h" @@ -419,20 +420,18 @@ static const Option controller_options[] = { 0, }}; -static const NMPlatformBridgeVlan ** -setting_vlans_to_platform(GPtrArray *array) +static NMPlatformBridgeVlan * +setting_vlans_to_platform(GPtrArray *array, guint *out_len) { - NMPlatformBridgeVlan **arr; - NMPlatformBridgeVlan *p_data; - guint i; + NMPlatformBridgeVlan *arr; + guint i; - if (!array || !array->len) + if (!array || !array->len) { + *out_len = 0; return NULL; + } - G_STATIC_ASSERT_EXPR(_nm_alignof(NMPlatformBridgeVlan *) >= _nm_alignof(NMPlatformBridgeVlan)); - arr = g_malloc((sizeof(NMPlatformBridgeVlan *) * (array->len + 1)) - + (sizeof(NMPlatformBridgeVlan) * (array->len))); - p_data = (NMPlatformBridgeVlan *) &arr[array->len + 1]; + arr = g_new(NMPlatformBridgeVlan, array->len); for (i = 0; i < array->len; i++) { NMBridgeVlan *vlan = array->pdata[i]; @@ -440,16 +439,16 @@ setting_vlans_to_platform(GPtrArray *array) nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end); - p_data[i] = (NMPlatformBridgeVlan){ + arr[i] = (NMPlatformBridgeVlan){ .vid_start = vid_start, .vid_end = vid_end, .pvid = nm_bridge_vlan_is_pvid(vlan), .untagged = nm_bridge_vlan_is_untagged(vlan), }; - arr[i] = &p_data[i]; } - arr[i] = NULL; - return (const NMPlatformBridgeVlan **) arr; + + *out_len = array->len; + return arr; } static void @@ -639,15 +638,16 @@ is_bridge_pvid_changed(NMDevice *device, NMSettingBridge *s_bridge) static gboolean bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is_reapply) { - NMDeviceBridge *self = NM_DEVICE_BRIDGE(device); - gconstpointer hwaddr; - size_t length; - gboolean enabled; - guint16 pvid; - NMPlatform *plat; - int ifindex; - gs_unref_ptrarray GPtrArray *vlans = NULL; - gs_free const NMPlatformBridgeVlan **plat_vlans = NULL; + NMDeviceBridge *self = NM_DEVICE_BRIDGE(device); + gconstpointer hwaddr; + size_t length; + gboolean enabled; + guint16 pvid; + NMPlatform *plat; + int ifindex; + gs_unref_ptrarray GPtrArray *vlans = NULL; + gs_free NMPlatformBridgeVlan *plat_vlans = NULL; + guint num_vlans; if (self->vlan_configured) return TRUE; @@ -664,7 +664,7 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is .vlan_filtering_val = FALSE, .vlan_default_pvid_has = TRUE, .vlan_default_pvid_val = 1})); - nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL); + nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL, 0); return TRUE; } @@ -696,7 +696,7 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is .vlan_default_pvid_val = 0})); /* Clear all existing VLANs */ - if (!nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL)) + if (!nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL, 0)) return FALSE; /* Now set the default PVID. After this point the kernel creates @@ -714,8 +714,9 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is /* Create VLANs only after setting the default PVID, so that * any PVID VLAN overrides the bridge's default PVID. */ g_object_get(s_bridge, NM_SETTING_BRIDGE_VLANS, &vlans, NULL); - plat_vlans = setting_vlans_to_platform(vlans); - if (plat_vlans && !nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, plat_vlans)) + plat_vlans = setting_vlans_to_platform(vlans, &num_vlans); + if (plat_vlans + && !nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, plat_vlans, num_vlans)) return FALSE; nm_platform_link_set_bridge_info(plat, @@ -728,6 +729,121 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge, gboolean is return TRUE; } +static NMPlatformBridgeVlan * +merge_bridge_vlan_default_pvid(NMPlatformBridgeVlan *vlans, guint *num_vlans, guint default_pvid) +{ + NMPlatformBridgeVlan *vlan; + gboolean has_pvid = FALSE; + guint i; + + for (i = 0; i < *num_vlans; i++) { + if (vlans[i].pvid) { + has_pvid = TRUE; + break; + } + } + + /* search if the list of VLANs already contains the default PVID */ + vlan = NULL; + for (i = 0; i < *num_vlans; i++) { + if (default_pvid >= vlans[i].vid_start && default_pvid <= vlans[i].vid_end) { + vlan = &vlans[i]; + break; + } + } + + if (!vlan) { + /* VLAN id not found, append the default PVID at the end. + * Set the PVID flag only if the port didn't have one. */ + vlans = g_realloc_n(vlans, *num_vlans + 1, sizeof(NMPlatformBridgeVlan)); + (*num_vlans)++; + vlans[*num_vlans - 1] = (NMPlatformBridgeVlan){ + .vid_start = default_pvid, + .vid_end = default_pvid, + .untagged = TRUE, + .pvid = !has_pvid, + }; + } + + return vlans; +} + +void +nm_device_reapply_bridge_port_vlans(NMDevice *device) +{ + NMDevice *self = device; /* for logging */ + NMSettingBridgePort *s_bridge_port; + NMDevice *controller; + NMSettingBridge *s_bridge; + gs_unref_ptrarray GPtrArray *tmp_vlans = NULL; + gs_free NMPlatformBridgeVlan *setting_vlans = NULL; + gs_free NMPlatformBridgeVlan *plat_vlans = NULL; + guint num_setting_vlans = 0; + guint num_plat_vlans = 0; + NMPlatform *plat; + int ifindex; + gboolean do_reapply; + + s_bridge_port = nm_device_get_applied_setting(device, NM_TYPE_SETTING_BRIDGE_PORT); + if (!s_bridge_port) + return; + + controller = nm_device_get_controller(device); + if (!controller) + return; + + s_bridge = nm_device_get_applied_setting(controller, NM_TYPE_SETTING_BRIDGE); + if (!s_bridge) + return; + + if (nm_setting_bridge_get_vlan_filtering(s_bridge)) { + g_object_get(s_bridge_port, NM_SETTING_BRIDGE_PORT_VLANS, &tmp_vlans, NULL); + setting_vlans = setting_vlans_to_platform(tmp_vlans, &num_setting_vlans); + + /* During a regular activation, we first set the default_pvid on the bridge + * (which creates the PVID VLAN on the port) and then add the VLANs on the port. + * This ensures that the PVID VLAN is inherited from the bridge, but it's + * overridden if the port specifies one. + * During a reapply on the port, we are not going to touch the bridge and + * so we need to merge manually the PVID from the bridge with the port VLANs. */ + setting_vlans = + merge_bridge_vlan_default_pvid(setting_vlans, + &num_setting_vlans, + nm_setting_bridge_get_vlan_default_pvid(s_bridge)); + } + + plat = nm_device_get_platform(device); + ifindex = nm_device_get_ifindex(device); + + if (!nm_platform_link_get_bridge_vlans(plat, ifindex, &plat_vlans, &num_plat_vlans)) { + _LOGD(LOGD_DEVICE, "reapply-bridge-port-vlans: can't get current VLANs from platform"); + do_reapply = TRUE; + } else { + nmp_utils_bridge_vlan_normalize(setting_vlans, &num_setting_vlans); + nmp_utils_bridge_vlan_normalize(plat_vlans, &num_plat_vlans); + if (!nmp_utils_bridge_normalized_vlans_equal(setting_vlans, + num_setting_vlans, + plat_vlans, + num_plat_vlans)) { + _LOGD(LOGD_DEVICE, "reapply-bridge-port-vlans: VLANs in platform need reapply"); + do_reapply = TRUE; + } else { + _LOGD(LOGD_DEVICE, "reapply-bridge-port-vlans: VLANs in platform didn't change"); + do_reapply = FALSE; + } + } + + if (do_reapply) { + nm_platform_link_set_bridge_vlans(plat, ifindex, TRUE, NULL, 0); + if (num_setting_vlans > 0) + nm_platform_link_set_bridge_vlans(plat, + ifindex, + TRUE, + setting_vlans, + num_setting_vlans); + } +} + static void _platform_lnk_bridge_init_from_setting(NMSettingBridge *s_bridge, NMPlatformLnkBridge *props) { @@ -937,13 +1053,14 @@ attach_port(NMDevice *device, bridge_set_vlan_options(device, s_bridge, FALSE); if (nm_setting_bridge_get_vlan_filtering(s_bridge)) { - gs_free const NMPlatformBridgeVlan **plat_vlans = NULL; - gs_unref_ptrarray GPtrArray *vlans = NULL; + gs_free NMPlatformBridgeVlan *plat_vlans = NULL; + gs_unref_ptrarray GPtrArray *vlans = NULL; + guint num_vlans; if (s_port) g_object_get(s_port, NM_SETTING_BRIDGE_PORT_VLANS, &vlans, NULL); - plat_vlans = setting_vlans_to_platform(vlans); + plat_vlans = setting_vlans_to_platform(vlans, &num_vlans); /* Since the link was just enportd, there are no existing VLANs * (except for the default one) and so there's no need to flush. */ @@ -952,7 +1069,8 @@ attach_port(NMDevice *device, && !nm_platform_link_set_bridge_vlans(nm_device_get_platform(port), nm_device_get_ifindex(port), TRUE, - plat_vlans)) + plat_vlans, + num_vlans)) return FALSE; } diff --git a/src/core/devices/nm-device-bridge.h b/src/core/devices/nm-device-bridge.h index 6d9f1614..f9be7580 100644 --- a/src/core/devices/nm-device-bridge.h +++ b/src/core/devices/nm-device-bridge.h @@ -27,4 +27,6 @@ extern const NMBtVTableNetworkServer *nm_bt_vtable_network_server; void _nm_device_bridge_notify_unregister_bt_nap(NMDevice *device, const char *reason); +void nm_device_reapply_bridge_port_vlans(NMDevice *device); + #endif /* __NETWORKMANAGER_DEVICE_BRIDGE_H__ */ diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 725241d5..799aca04 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -13881,6 +13881,8 @@ check_and_reapply_connection(NMDevice *self, if (priv->state >= NM_DEVICE_STATE_ACTIVATED) nm_device_update_metered(self); + nm_device_reapply_bridge_port_vlans(self); + sett_conn = nm_device_get_settings_connection(self); if (sett_conn) { nm_settings_connection_autoconnect_blocked_reason_set( diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c index ac7e97f0..59c8f54c 100644 --- a/src/core/devices/nm-lldp-listener.c +++ b/src/core/devices/nm-lldp-listener.c @@ -704,9 +704,16 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh) /*****************************************************************************/ +static void +nmtst_lldp_event_handler(NMLldpRX *lldp, NMLldpRXEvent event, NMLldpNeighbor *n, void *user_data) +{ + g_assert_not_reached(); +} + GVariant * nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len) { + nm_auto(nm_lldp_rx_unrefp) NMLldpRX *lldp_rx = NULL; nm_auto(nm_lldp_neighbor_unrefp) NMLldpNeighbor *neighbor_nm = NULL; nm_auto(lldp_neighbor_freep) LldpNeighbor *neigh = NULL; GVariant *variant; @@ -714,7 +721,13 @@ nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len) g_assert(raw_data); g_assert(raw_len > 0); - neighbor_nm = nm_lldp_neighbor_new_from_raw(raw_data, raw_len); + lldp_rx = nm_lldp_rx_new(&((NMLldpRXConfig){ + .ifindex = 1, + .neighbors_max = MAX_NEIGHBORS, + .callback = nmtst_lldp_event_handler, + })); + + neighbor_nm = nm_lldp_neighbor_new_from_raw(lldp_rx, raw_data, raw_len); g_assert(neighbor_nm); neigh = lldp_neighbor_new(neighbor_nm); diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c index 694f5905..93b52526 100644 --- a/src/core/nm-policy.c +++ b/src/core/nm-policy.c @@ -17,6 +17,7 @@ #include "NetworkManagerUtils.h" #include "devices/nm-device.h" +#include "devices/nm-device-factory.h" #include "dns/nm-dns-manager.h" #include "nm-act-request.h" #include "nm-auth-utils.h" @@ -47,6 +48,10 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMPolicy, PROP_ACTIVATING_IP4_AC, PROP_ACTIVATING_IP6_AC, ); +#define HOSTNAME_RETRY_INTERVAL_MIN 30U +#define HOSTNAME_RETRY_INTERVAL_MAX (60U * 60 * 12) /* 12 hours */ +#define HOSTNAME_RETRY_INTERVAL_MULTIPLIER 8U + typedef struct { NMManager *manager; NMNetns *netns; @@ -78,14 +83,21 @@ typedef struct { char *orig_hostname; /* hostname at NM start time */ char *cur_hostname; /* hostname we want to assign */ char *cur_hostname_full; /* similar to @last_hostname, but before shortening */ - char * - last_hostname; /* last hostname NM set (to detect if someone else changed it in the meanwhile) */ + char *last_hostname; /* last hostname NM set (to detect if someone else + * changed it in the meanwhile) */ + struct { + GSource *source; + guint interval_sec; + gboolean do_restart; /* when something changes, set this to TRUE so that the next retry + * will restart from the lowest timeout. */ + } hostname_retry; bool changing_hostname : 1; /* hostname set operation in progress */ bool dhcp_hostname : 1; /* current hostname was set from dhcp */ bool updating_dns : 1; GArray *ip6_prefix_delegations; /* pool of ip6 prefixes delegated to all devices */ + } NMPolicyPrivate; struct _NMPolicy { @@ -134,9 +146,10 @@ _PRIV_TO_SELF(NMPolicyPrivate *priv) /*****************************************************************************/ -static void update_system_hostname(NMPolicy *self, const char *msg); -static void nm_policy_device_recheck_auto_activate_all_schedule(NMPolicy *self); +static void update_system_hostname(NMPolicy *self, const char *msg, gboolean reset_retry_interval); +static void nm_policy_device_recheck_auto_activate_all_schedule(NMPolicy *self); static NMDevice *get_default_device(NMPolicy *self, int addr_family); +static gboolean hostname_retry_cb(gpointer user_data); /*****************************************************************************/ @@ -557,7 +570,56 @@ _get_hostname(NMPolicy *self) } static void -_set_hostname(NMPolicy *self, const char *new_hostname, const char *msg) +hostname_retry_schedule(NMPolicy *self) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + + if (priv->hostname_retry.source && !priv->hostname_retry.do_restart) + return; + + nm_clear_g_source_inst(&priv->hostname_retry.source); + + if (priv->hostname_retry.do_restart) + priv->hostname_retry.interval_sec = 0; + + priv->hostname_retry.interval_sec *= HOSTNAME_RETRY_INTERVAL_MULTIPLIER; + priv->hostname_retry.interval_sec = NM_CLAMP(priv->hostname_retry.interval_sec, + HOSTNAME_RETRY_INTERVAL_MIN, + HOSTNAME_RETRY_INTERVAL_MAX); + + _LOGT(LOGD_DNS, + "hostname-retry: schedule in %u seconds%s", + priv->hostname_retry.interval_sec, + priv->hostname_retry.do_restart ? " (restarted)" : ""); + priv->hostname_retry.source = + nm_g_timeout_add_seconds_source(priv->hostname_retry.interval_sec, hostname_retry_cb, self); + + priv->hostname_retry.do_restart = FALSE; +} + +static gboolean +hostname_retry_cb(gpointer user_data) +{ + NMPolicy *self = NM_POLICY(user_data); + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + const CList *tmp_lst; + NMDevice *device; + + _LOGT(LOGD_DNS, "hostname-retry: timeout"); + + nm_clear_g_source_inst(&priv->hostname_retry.source); + + /* Clear any cached DNS results before retrying */ + nm_manager_for_each_device (priv->manager, device, tmp_lst) { + nm_device_clear_dns_lookup_data(device, "hostname retry timeout"); + } + update_system_hostname(self, "hostname retry timeout", FALSE); + + return G_SOURCE_CONTINUE; +} + +static void +_set_hostname(NMPolicy *self, const char *new_hostname, const char *msg, gboolean do_retry) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); gs_free char *old_hostname = NULL; @@ -611,6 +673,15 @@ _set_hostname(NMPolicy *self, const char *new_hostname, const char *msg) priv->updating_dns = FALSE; } + if (!do_retry) { + _LOGT(LOGD_DNS, "hostname-retry: clear"); + nm_clear_g_source_inst(&priv->hostname_retry.source); + priv->hostname_retry.interval_sec = 0; + priv->hostname_retry.do_restart = FALSE; + } else if (!priv->hostname_retry.source) { + hostname_retry_schedule(self); + } + /* Finally, set kernel hostname */ nm_assert(!priv->cur_hostname || priv->cur_hostname[0]); name = priv->cur_hostname ?: FALLBACK_HOSTNAME4; @@ -796,7 +867,7 @@ device_dns_lookup_done(NMDevice *device, gpointer user_data) g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self); - update_system_hostname(self, "lookup finished"); + update_system_hostname(self, "lookup finished", FALSE); } static void @@ -809,12 +880,28 @@ device_carrier_changed(NMDevice *device, GParamSpec *pspec, gpointer user_data) if (nm_device_has_carrier(device)) { g_signal_handlers_disconnect_by_func(device, device_carrier_changed, priv); msg = g_strdup_printf("device '%s' got carrier", nm_device_get_iface(device)); - update_system_hostname(self, msg); + update_system_hostname(self, msg, TRUE); } } +/* + * This function evaluates different sources (static configuration, DHCP, DNS, ...) + * to set the system hostname. + * + * When the function needs to perform a blocking action like a DNS resolution, it + * subscribes to a signal for the completion event, registering a callback that + * invokes this function again. In the new invocation, any previous DNS result is + * cached and doesn't need a new resolution. + * + * In case no hostname is found when after sources have been evaluated, it schedules + * a timer to retry later with an interval that is increased at each attempt. When + * this function is called after something changed (for example, carrier went up, a + * new address was added), @reset_retry_interval should be set to TRUE so that the + * next retry will use the smallest interval. In this way, it can quickly adapt to + * temporary misconfigurations at boot or when the network environment changes. + */ static void -update_system_hostname(NMPolicy *self, const char *msg) +update_system_hostname(NMPolicy *self, const char *msg, gboolean reset_retry_interval) { NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); const char *configured_hostname; @@ -829,6 +916,9 @@ update_system_hostname(NMPolicy *self, const char *msg) g_return_if_fail(self != NULL); + if (reset_retry_interval) + priv->hostname_retry.do_restart = TRUE; + if (priv->hostname_mode == NM_POLICY_HOSTNAME_MODE_NONE) { _LOGT(LOGD_DNS, "set-hostname: hostname is unmanaged"); return; @@ -871,7 +961,7 @@ update_system_hostname(NMPolicy *self, const char *msg) /* Try a persistent hostname first */ configured_hostname = nm_hostname_manager_get_static_hostname(priv->hostname_manager); if (configured_hostname && nm_utils_is_specific_hostname(configured_hostname)) { - _set_hostname(self, configured_hostname, "from system configuration"); + _set_hostname(self, configured_hostname, "from system configuration", FALSE); priv->dhcp_hostname = FALSE; return; } @@ -908,7 +998,10 @@ update_system_hostname(NMPolicy *self, const char *msg) if (dhcp_hostname && dhcp_hostname[0]) { p = nm_str_skip_leading_spaces(dhcp_hostname); if (p[0]) { - _set_hostname(self, p, info->IS_IPv4 ? "from DHCPv4" : "from DHCPv6"); + _set_hostname(self, + p, + info->IS_IPv4 ? "from DHCPv4" : "from DHCPv6", + FALSE); priv->dhcp_hostname = TRUE; return; } @@ -936,7 +1029,7 @@ update_system_hostname(NMPolicy *self, const char *msg) priv); } if (result) { - _set_hostname(self, result, "from address lookup"); + _set_hostname(self, result, "from address lookup", FALSE); return; } if (wait) { @@ -951,8 +1044,10 @@ update_system_hostname(NMPolicy *self, const char *msg) } /* If an hostname was set outside NetworkManager keep it */ - if (external_hostname) + if (external_hostname) { + hostname_retry_schedule(self); return; + } if (priv->hostname_mode == NM_POLICY_HOSTNAME_MODE_DHCP) { /* In dhcp hostname-mode, the hostname is updated only if it comes from @@ -961,7 +1056,7 @@ update_system_hostname(NMPolicy *self, const char *msg) * so reset the hostname to the previous value */ if (priv->dhcp_hostname) { - _set_hostname(self, priv->orig_hostname, "reset dhcp hostname"); + _set_hostname(self, priv->orig_hostname, "reset dhcp hostname", TRUE); priv->dhcp_hostname = FALSE; } return; @@ -973,11 +1068,11 @@ update_system_hostname(NMPolicy *self, const char *msg) * set externally to NM */ if (priv->orig_hostname) { - _set_hostname(self, priv->orig_hostname, "from system startup"); + _set_hostname(self, priv->orig_hostname, "from system startup", TRUE); return; } - _set_hostname(self, NULL, "no hostname found"); + _set_hostname(self, NULL, "no hostname found", TRUE); } static void @@ -1254,7 +1349,7 @@ update_routing_and_dns(NMPolicy *self, gboolean force_update, NMDevice *changed_ update_ip6_routing(self, force_update); /* Update the system hostname */ - update_system_hostname(self, "routing and dns"); + update_system_hostname(self, "routing and dns", FALSE); nm_dns_manager_end_updates(priv->dns_manager, __func__); } @@ -1571,7 +1666,7 @@ _static_hostname_changed_cb(NMHostnameManager *hostname_manager, NMPolicyPrivate *priv = user_data; NMPolicy *self = _PRIV_TO_SELF(priv); - update_system_hostname(self, "hostname changed"); + update_system_hostname(self, "hostname changed", FALSE); } void @@ -1773,6 +1868,74 @@ _connection_autoconnect_retries_set(NMPolicy *self, } } +static void +unblock_autoconnect_for_children(NMPolicy *self, + const char *parent_device, + const char *parent_uuid_settings, + const char *parent_uuid_applied, + const char *parent_mac_addr, + gboolean reset_devcon_autoconnect) +{ + NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self); + NMSettingsConnection *const *connections; + gboolean changed; + guint i; + + _LOGT(LOGD_CORE, + "block-autoconnect: unblocking child profiles for parent ifname=%s%s%s, uuid=%s%s%s" + "%s%s%s", + NM_PRINT_FMT_QUOTE_STRING(parent_device), + NM_PRINT_FMT_QUOTE_STRING(parent_uuid_settings), + NM_PRINT_FMT_QUOTED(parent_uuid_applied, + ", applied-uuid=\"", + parent_uuid_applied, + "\"", + "")); + + changed = FALSE; + connections = nm_settings_get_connections(priv->settings, NULL); + for (i = 0; connections[i]; i++) { + NMSettingsConnection *sett_conn = connections[i]; + NMConnection *connection; + NMDeviceFactory *factory; + const char *parent_name = NULL; + + connection = nm_settings_connection_get_connection(sett_conn); + factory = nm_device_factory_manager_find_factory_for_connection(connection); + if (factory) + parent_name = nm_device_factory_get_connection_parent(factory, connection); + + if (!parent_name) + continue; + + if (!NM_IN_STRSET(parent_name, + parent_device, + parent_uuid_applied, + parent_uuid_settings, + parent_mac_addr)) + continue; + + if (reset_devcon_autoconnect) { + if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn)) + changed = TRUE; + } + + /* unblock the devices associated with that connection */ + if (nm_manager_devcon_autoconnect_blocked_reason_set( + priv->manager, + NULL, + sett_conn, + NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, + FALSE)) { + if (!nm_settings_connection_autoconnect_is_blocked(sett_conn)) + changed = TRUE; + } + } + + if (changed) + nm_policy_device_recheck_auto_activate_all_schedule(self); +} + static void unblock_autoconnect_for_ports(NMPolicy *self, const char *controller_device, @@ -1856,16 +2019,21 @@ unblock_autoconnect_for_ports_for_sett_conn(NMPolicy *self, NMSettingsConnection } static void -activate_slave_connections(NMPolicy *self, NMDevice *device) -{ - const char *master_device; - const char *master_uuid_settings = NULL; - const char *master_uuid_applied = NULL; +activate_port_or_children_connections(NMPolicy *self, + NMDevice *device, + gboolean activate_children_connections_only) +{ + const char *controller_device; + const char *controller_uuid_settings = NULL; + const char *controller_uuid_applied = NULL; + const char *parent_mac_addr = NULL; NMActRequest *req; gboolean internal_activation = FALSE; - master_device = nm_device_get_iface(device); - nm_assert(master_device); + controller_device = nm_device_get_iface(device); + nm_assert(controller_device); + + parent_mac_addr = nm_device_get_permanent_hw_address(device); req = nm_device_get_act_request(device); if (req) { @@ -1875,25 +2043,33 @@ activate_slave_connections(NMPolicy *self, NMDevice *device) sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req)); if (sett_conn) - master_uuid_settings = nm_settings_connection_get_uuid(sett_conn); + controller_uuid_settings = nm_settings_connection_get_uuid(sett_conn); connection = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req)); if (connection) - master_uuid_applied = nm_connection_get_uuid(connection); + controller_uuid_applied = nm_connection_get_uuid(connection); - if (nm_streq0(master_uuid_settings, master_uuid_applied)) - master_uuid_applied = NULL; + if (nm_streq0(controller_uuid_settings, controller_uuid_applied)) + controller_uuid_applied = NULL; subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req)); internal_activation = subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL); } - unblock_autoconnect_for_ports(self, - master_device, - master_uuid_settings, - master_uuid_applied, - !internal_activation); + if (!activate_children_connections_only) { + unblock_autoconnect_for_ports(self, + controller_device, + controller_uuid_settings, + controller_uuid_applied, + !internal_activation); + } + unblock_autoconnect_for_children(self, + controller_device, + controller_uuid_settings, + controller_uuid_applied, + parent_mac_addr, + !internal_activation); } static gboolean @@ -2061,13 +2237,12 @@ device_state_changed(NMDevice *device, } break; case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED: - /* A connection that fails due to dependency-failed is not - * able to reconnect until the master connection activates - * again; when this happens, the master clears the blocked - * reason for all its slaves in activate_slave_connections() - * and tries to reconnect them. For this to work, the slave - * should be marked as blocked when it fails with - * dependency-failed. + /* A connection that fails due to dependency-failed is not able to + * reconnect until the connection it depends on activates again; + * when this happens, the controller or parent clears the blocked + * reason for all its dependent devices in activate_port_or_children_connections() + * and tries to reconnect them. For this to work, the port should + * be marked as blocked when it fails with dependency-failed. */ _LOGD(LOGD_DEVICE, "block-autoconnect: connection[%p] (%s) now blocked from autoconnect due to " @@ -2111,6 +2286,11 @@ device_state_changed(NMDevice *device, } break; case NM_DEVICE_STATE_ACTIVATED: + if (nm_device_get_device_type(device) == NM_DEVICE_TYPE_OVS_INTERFACE) { + /* When parent is ovs-interface, the kernel link is only created in stage3, we have to + * delay unblocking the children and schedule them for activation until parent is activated */ + activate_port_or_children_connections(self, device, TRUE); + } if (sett_conn) { /* Reset auto retries back to default since connection was successful */ nm_manager_devcon_autoconnect_retries_reset(priv->manager, device, sett_conn); @@ -2133,7 +2313,7 @@ device_state_changed(NMDevice *device, update_ip_dns(self, AF_INET6, device); update_ip4_routing(self, TRUE); update_ip6_routing(self, TRUE); - update_system_hostname(self, "routing and dns"); + update_system_hostname(self, "routing and dns", TRUE); nm_dns_manager_end_updates(priv->dns_manager, __func__); break; @@ -2195,9 +2375,9 @@ device_state_changed(NMDevice *device, break; case NM_DEVICE_STATE_PREPARE: - /* Reset auto-connect retries of all slaves and schedule them for + /* Reset auto-connect retries of all ports or children and schedule them for * activation. */ - activate_slave_connections(self, device); + activate_port_or_children_connections(self, device, FALSE); /* Now that the device state is progressing, we don't care * anymore for the AC state. */ @@ -2281,7 +2461,7 @@ device_l3cd_changed(NMDevice *device, update_ip6_routing(self, TRUE); /* FIXME: since we already monitor platform addresses changes, * this is probably no longer necessary? */ - update_system_hostname(self, "ip conf"); + update_system_hostname(self, "ip conf", FALSE); } else { nm_dns_manager_set_ip_config(priv->dns_manager, AF_UNSPEC, @@ -2303,7 +2483,7 @@ device_platform_address_changed(NMDevice *device, gpointer user_data) state = nm_device_get_state(device); if (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_DEACTIVATING) { - update_system_hostname(self, "address changed"); + update_system_hostname(self, "address changed", TRUE); } } @@ -2642,7 +2822,7 @@ dns_config_changed(NMDnsManager *dns_manager, gpointer user_data) nm_device_clear_dns_lookup_data(device, "DNS configuration changed"); } - update_system_hostname(self, "DNS configuration changed"); + update_system_hostname(self, "DNS configuration changed", FALSE); } nm_dispatcher_call_dns_change(); @@ -2913,7 +3093,7 @@ constructed(GObject *object) G_OBJECT_CLASS(nm_policy_parent_class)->constructed(object); _LOGD(LOGD_DNS, "hostname-mode: %s", _hostname_mode_to_string(priv->hostname_mode)); - update_system_hostname(self, "initial hostname"); + update_system_hostname(self, "initial hostname", FALSE); } NMPolicy * @@ -2971,6 +3151,7 @@ dispose(GObject *object) nm_clear_g_source_inst(&priv->reset_connections_retries_idle_source); nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source); + nm_clear_g_source_inst(&priv->hostname_retry.source); nm_clear_g_free(&priv->orig_hostname); nm_clear_g_free(&priv->cur_hostname); diff --git a/src/libnm-lldp/nm-lldp-neighbor.c b/src/libnm-lldp/nm-lldp-neighbor.c index 0379cf38..3a76ea31 100644 --- a/src/libnm-lldp/nm-lldp-neighbor.c +++ b/src/libnm-lldp/nm-lldp-neighbor.c @@ -65,6 +65,7 @@ parse_string(NMLldpRX *lldp_rx, char **s, const void *q, size_t n) const char *p = q; char *k; + nm_assert(lldp_rx); nm_assert(s); nm_assert(p || n == 0); @@ -99,31 +100,33 @@ parse_string(NMLldpRX *lldp_rx, char **s, const void *q, size_t n) } int -nm_lldp_neighbor_parse(NMLldpNeighbor *n) +nm_lldp_neighbor_parse(NMLldpRX *lldp_rx, NMLldpNeighbor *n) { struct ether_header h; const uint8_t *p; size_t left; int r; + nm_assert(lldp_rx); nm_assert(n); + nm_assert(!n->lldp_rx); if (n->raw_size < sizeof(struct ether_header)) { - _LOG2D(n->lldp_rx, "Received truncated packet, ignoring."); + _LOG2D(lldp_rx, "Received truncated packet, ignoring."); return -NME_UNSPEC; } memcpy(&h, NM_LLDP_NEIGHBOR_RAW(n), sizeof(h)); if (h.ether_type != htobe16(NM_ETHERTYPE_LLDP)) { - _LOG2D(n->lldp_rx, "Received packet with wrong type, ignoring."); + _LOG2D(lldp_rx, "Received packet with wrong type, ignoring."); return -NME_UNSPEC; } if (h.ether_dhost[0] != 0x01 || h.ether_dhost[1] != 0x80 || h.ether_dhost[2] != 0xc2 || h.ether_dhost[3] != 0x00 || h.ether_dhost[4] != 0x00 || !NM_IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) { - _LOG2D(n->lldp_rx, "Received packet with wrong destination address, ignoring."); + _LOG2D(lldp_rx, "Received packet with wrong destination address, ignoring."); return -NME_UNSPEC; } @@ -138,7 +141,7 @@ nm_lldp_neighbor_parse(NMLldpNeighbor *n) uint16_t length; if (left < 2) { - _LOG2D(n->lldp_rx, "TLV lacks header, ignoring."); + _LOG2D(lldp_rx, "TLV lacks header, ignoring."); return -NME_UNSPEC; } @@ -147,14 +150,14 @@ nm_lldp_neighbor_parse(NMLldpNeighbor *n) p += 2, left -= 2; if (left < length) { - _LOG2D(n->lldp_rx, "TLV truncated, ignoring datagram."); + _LOG2D(lldp_rx, "TLV truncated, ignoring datagram."); return -NME_UNSPEC; } switch (type) { case NM_LLDP_TYPE_END: if (length != 0) { - _LOG2D(n->lldp_rx, "End marker TLV not zero-sized, ignoring datagram."); + _LOG2D(lldp_rx, "End marker TLV not zero-sized, ignoring datagram."); return -NME_UNSPEC; } @@ -166,12 +169,12 @@ nm_lldp_neighbor_parse(NMLldpNeighbor *n) case NM_LLDP_TYPE_CHASSIS_ID: if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */ - _LOG2D(n->lldp_rx, "Chassis ID field size out of range, ignoring datagram."); + _LOG2D(lldp_rx, "Chassis ID field size out of range, ignoring datagram."); return -NME_UNSPEC; } if (n->id.chassis_id) { - _LOG2D(n->lldp_rx, "Duplicate chassis ID field, ignoring datagram."); + _LOG2D(lldp_rx, "Duplicate chassis ID field, ignoring datagram."); return -NME_UNSPEC; } @@ -182,12 +185,12 @@ nm_lldp_neighbor_parse(NMLldpNeighbor *n) case NM_LLDP_TYPE_PORT_ID: if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */ - _LOG2D(n->lldp_rx, "Port ID field size out of range, ignoring datagram."); + _LOG2D(lldp_rx, "Port ID field size out of range, ignoring datagram."); return -NME_UNSPEC; } if (n->id.port_id) { - _LOG2D(n->lldp_rx, "Duplicate port ID field, ignoring datagram."); + _LOG2D(lldp_rx, "Duplicate port ID field, ignoring datagram."); return -NME_UNSPEC; } @@ -197,12 +200,12 @@ nm_lldp_neighbor_parse(NMLldpNeighbor *n) case NM_LLDP_TYPE_TTL: if (length != 2) { - _LOG2D(n->lldp_rx, "TTL field has wrong size, ignoring datagram."); + _LOG2D(lldp_rx, "TTL field has wrong size, ignoring datagram."); return -NME_UNSPEC; } if (n->has_ttl) { - _LOG2D(n->lldp_rx, "Duplicate TTL field, ignoring datagram."); + _LOG2D(lldp_rx, "Duplicate TTL field, ignoring datagram."); return -NME_UNSPEC; } @@ -211,26 +214,26 @@ nm_lldp_neighbor_parse(NMLldpNeighbor *n) break; case NM_LLDP_TYPE_PORT_DESCRIPTION: - r = parse_string(n->lldp_rx, &n->port_description, p, length); + r = parse_string(lldp_rx, &n->port_description, p, length); if (r < 0) return r; break; case NM_LLDP_TYPE_SYSTEM_NAME: - r = parse_string(n->lldp_rx, &n->system_name, p, length); + r = parse_string(lldp_rx, &n->system_name, p, length); if (r < 0) return r; break; case NM_LLDP_TYPE_SYSTEM_DESCRIPTION: - r = parse_string(n->lldp_rx, &n->system_description, p, length); + r = parse_string(lldp_rx, &n->system_description, p, length); if (r < 0) return r; break; case NM_LLDP_TYPE_SYSTEM_CAPABILITIES: if (length != 4) { - _LOG2D(n->lldp_rx, "System capabilities field has wrong size."); + _LOG2D(lldp_rx, "System capabilities field has wrong size."); return -NME_UNSPEC; } @@ -241,13 +244,13 @@ nm_lldp_neighbor_parse(NMLldpNeighbor *n) case NM_LLDP_TYPE_PRIVATE: if (length < 4) { - _LOG2D(n->lldp_rx, "Found private TLV that is too short, ignoring."); + _LOG2D(lldp_rx, "Found private TLV that is too short, ignoring."); return -NME_UNSPEC; } /* RFC 8520: MUD URL */ if (memcmp(p, NM_LLDP_OUI_IANA_MUD, sizeof(NM_LLDP_OUI_IANA_MUD)) == 0) { - r = parse_string(n->lldp_rx, + r = parse_string(lldp_rx, &n->mud_url, p + sizeof(NM_LLDP_OUI_IANA_MUD), length - sizeof(NM_LLDP_OUI_IANA_MUD)); @@ -262,7 +265,7 @@ nm_lldp_neighbor_parse(NMLldpNeighbor *n) end_marker: if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) { - _LOG2D(n->lldp_rx, "One or more mandatory TLV missing in datagram. Ignoring."); + _LOG2D(lldp_rx, "One or more mandatory TLV missing in datagram. Ignoring."); return -NME_UNSPEC; } @@ -741,7 +744,7 @@ nm_lldp_neighbor_new(size_t raw_size) } NMLldpNeighbor * -nm_lldp_neighbor_new_from_raw(const void *raw, size_t raw_size) +nm_lldp_neighbor_new_from_raw(NMLldpRX *lldp_rx, const void *raw, size_t raw_size) { nm_auto(nm_lldp_neighbor_unrefp) NMLldpNeighbor *n = NULL; int r; @@ -752,7 +755,7 @@ nm_lldp_neighbor_new_from_raw(const void *raw, size_t raw_size) nm_memcpy(NM_LLDP_NEIGHBOR_RAW(n), raw, raw_size); - r = nm_lldp_neighbor_parse(n); + r = nm_lldp_neighbor_parse(lldp_rx, n); if (r < 0) return NULL; diff --git a/src/libnm-lldp/nm-lldp-neighbor.h b/src/libnm-lldp/nm-lldp-neighbor.h index 1adc967e..038591a0 100644 --- a/src/libnm-lldp/nm-lldp-neighbor.h +++ b/src/libnm-lldp/nm-lldp-neighbor.h @@ -75,11 +75,13 @@ NM_LLDP_NEIGHBOR_TLV_DATA(const NMLldpNeighbor *n) return ((uint8_t *) NM_LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2; } +struct _NMLldpRX; + int nm_lldp_neighbor_prioq_compare_func(const void *a, const void *b); void nm_lldp_neighbor_unlink(NMLldpNeighbor *n); NMLldpNeighbor *nm_lldp_neighbor_new(size_t raw_size); -int nm_lldp_neighbor_parse(NMLldpNeighbor *n); +int nm_lldp_neighbor_parse(struct _NMLldpRX *lldp_rx, NMLldpNeighbor *n); void nm_lldp_neighbor_start_ttl(NMLldpNeighbor *n); #endif /* __NM_LLDP_NEIGHBOR_H__ */ diff --git a/src/libnm-lldp/nm-lldp-rx-internal.h b/src/libnm-lldp/nm-lldp-rx-internal.h index 47d063ae..1296a9d3 100644 --- a/src/libnm-lldp/nm-lldp-rx-internal.h +++ b/src/libnm-lldp/nm-lldp-rx-internal.h @@ -34,7 +34,7 @@ struct _NMLldpRX { NMLldpRX *_lldp_rx = (lldp_rx); \ \ if (_NMLOG2_ENABLED(_level)) { \ - _nm_log(level, \ + _nm_log(_level, \ _NMLOG2_DOMAIN, \ 0, \ _lldp_rx->config.log_ifname, \ diff --git a/src/libnm-lldp/nm-lldp-rx.c b/src/libnm-lldp/nm-lldp-rx.c index 345c6d56..90414b3e 100644 --- a/src/libnm-lldp/nm-lldp-rx.c +++ b/src/libnm-lldp/nm-lldp-rx.c @@ -255,7 +255,7 @@ lldp_rx_receive_datagram(int fd, GIOCondition condition, gpointer user_data) } else n->timestamp_usec = nm_utils_get_monotonic_timestamp_usec(); - r = nm_lldp_neighbor_parse(n); + r = nm_lldp_neighbor_parse(lldp_rx, n); if (r < 0) { _LOG2D(lldp_rx, "Failure parsing invalid LLDP datagram."); return G_SOURCE_CONTINUE; diff --git a/src/libnm-lldp/nm-lldp-rx.h b/src/libnm-lldp/nm-lldp-rx.h index a3f38053..d96ffcd8 100644 --- a/src/libnm-lldp/nm-lldp-rx.h +++ b/src/libnm-lldp/nm-lldp-rx.h @@ -68,7 +68,7 @@ NMLldpNeighbor **nm_lldp_rx_get_neighbors(NMLldpRX *lldp_rx, guint *out_len); /*****************************************************************************/ -NMLldpNeighbor *nm_lldp_neighbor_new_from_raw(const void *raw, size_t raw_size); +NMLldpNeighbor *nm_lldp_neighbor_new_from_raw(NMLldpRX *lldp_rx, const void *raw, size_t raw_size); NMLldpNeighbor *nm_lldp_neighbor_ref(NMLldpNeighbor *n); NMLldpNeighbor *nm_lldp_neighbor_unref(NMLldpNeighbor *n); diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index dd4be614..53f678fc 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -187,6 +187,9 @@ G_STATIC_ASSERT(RTA_MAX == (__RTA_MAX - 1)); /*****************************************************************************/ +/* Added in kernel 5.19, dated July 31, 2022 */ +#define IFLA_BOND_SLAVE_PRIO 9 + #define IFLA_BOND_ACTIVE_PORT IFLA_BOND_ACTIVE_SLAVE #define IFLA_BOND_PORT_PRIO IFLA_BOND_SLAVE_PRIO #define IFLA_BOND_ALL_PORTS_ACTIVE IFLA_BOND_ALL_SLAVES_ACTIVE @@ -353,7 +356,8 @@ struct _ifla_vf_vlan_info { /*****************************************************************************/ -#define RESYNC_RETRIES 50 +#define RESYNC_RETRIES 50 +#define RESYNC_BACKOFF_SECONDS 1 /*****************************************************************************/ @@ -9402,17 +9406,20 @@ nla_put_failure: } static gboolean -link_set_bridge_vlans(NMPlatform *platform, - int ifindex, - gboolean on_controller, - const NMPlatformBridgeVlan *const *vlans) +link_set_bridge_vlans(NMPlatform *platform, + int ifindex, + gboolean on_controller, + const NMPlatformBridgeVlan *vlans, + guint num_vlans) { nm_auto_nlmsg struct nl_msg *nlmsg = NULL; struct nlattr *list; struct bridge_vlan_info vinfo = {}; guint i; - nlmsg = _nl_msg_new_link_full(vlans ? RTM_SETLINK : RTM_DELLINK, + nm_assert(num_vlans == 0 || vlans); + + nlmsg = _nl_msg_new_link_full(num_vlans > 0 ? RTM_SETLINK : RTM_DELLINK, 0, ifindex, NULL, @@ -9430,10 +9437,10 @@ link_set_bridge_vlans(NMPlatform *platform, IFLA_BRIDGE_FLAGS, on_controller ? BRIDGE_FLAGS_CONTROLLER : BRIDGE_FLAGS_SELF); - if (vlans) { + if (num_vlans > 0) { /* Add VLANs */ - for (i = 0; vlans[i]; i++) { - const NMPlatformBridgeVlan *vlan = vlans[i]; + for (i = 0; i < num_vlans; i++) { + const NMPlatformBridgeVlan *vlan = &vlans[i]; gboolean is_range = vlan->vid_start != vlan->vid_end; vinfo.vid = vlan->vid_start; @@ -9470,6 +9477,138 @@ nla_put_failure: g_return_val_if_reached(FALSE); } +typedef struct { + int ifindex; + GArray *vlans; +} BridgeVlanData; + +static int +get_bridge_vlans_cb(const struct nl_msg *msg, void *arg) +{ + static const struct nla_policy policy[] = { + [IFLA_AF_SPEC] = {.type = NLA_NESTED}, + }; + struct nlattr *tb[G_N_ELEMENTS(policy)]; + gboolean is_range = FALSE; + BridgeVlanData *data = arg; + struct ifinfomsg *ifinfo; + struct nlattr *attr; + int rem; + + if (nlmsg_parse_arr(nlmsg_hdr(msg), sizeof(struct ifinfomsg), tb, policy) < 0) + return NL_SKIP; + + ifinfo = NLMSG_DATA(nlmsg_hdr(msg)); + if (ifinfo->ifi_index != data->ifindex) + return NL_SKIP; + + if (!tb[IFLA_AF_SPEC]) + return NL_SKIP; + + nla_for_each_nested (attr, tb[IFLA_AF_SPEC], rem) { + struct bridge_vlan_info vlan_info; + NMPlatformBridgeVlan vlan = {}; + + if (nla_type(attr) != IFLA_BRIDGE_VLAN_INFO) + continue; + + if (!data->vlans) + data->vlans = g_array_new(0, FALSE, sizeof(NMPlatformBridgeVlan)); + + vlan_info = *nla_data_as(struct bridge_vlan_info, attr); + + if (is_range) { + nm_g_array_index(data->vlans, NMPlatformBridgeVlan, data->vlans->len - 1).vid_end = + vlan_info.vid; + is_range = FALSE; + continue; + } else { + vlan.vid_start = vlan_info.vid; + vlan.vid_end = vlan_info.vid; + vlan.untagged = vlan_info.flags & BRIDGE_VLAN_INFO_UNTAGGED; + vlan.pvid = vlan_info.flags & BRIDGE_VLAN_INFO_PVID; + + if (vlan_info.flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) + is_range = TRUE; + } + + g_array_append_val(data->vlans, vlan); + } + + return NL_OK; +} + +static gboolean +link_get_bridge_vlans(NMPlatform *platform, + int ifindex, + NMPlatformBridgeVlan **out_vlans, + guint *out_num_vlans) +{ + gboolean ret = FALSE; + nm_auto_nlmsg struct nl_msg *nlmsg = NULL; + struct nl_sock *sk = NULL; + BridgeVlanData data; + int nle; + + nlmsg = _nl_msg_new_link_full(RTM_GETLINK, NLM_F_DUMP, 0, NULL, AF_BRIDGE, 0, 0, 0); + if (!nlmsg) + g_return_val_if_reached(FALSE); + + nle = nl_socket_new(&sk, NETLINK_ROUTE, NL_SOCKET_FLAGS_DISABLE_MSG_PEEK, 0, 0); + if (nle < 0) { + _LOGD("get-bridge-vlan: error opening socket: %s (%d)", nm_strerror(nle), nle); + ret = FALSE; + goto err; + } + + NLA_PUT_U32(nlmsg, IFLA_EXT_MASK, RTEXT_FILTER_BRVLAN_COMPRESSED); + + nle = nl_send_auto(sk, nlmsg); + if (nle < 0) { + _LOGD("get-bridge-vlans: failed sending request: %s (%d)", nm_strerror(nle), nle); + ret = FALSE; + goto err; + } + + data = ((BridgeVlanData){ + .ifindex = ifindex, + }); + + do { + nle = nl_recvmsgs(sk, + &((const struct nl_cb){ + .valid_cb = get_bridge_vlans_cb, + .valid_arg = &data, + })); + } while (nle == -EAGAIN); + + if (nle < 0) { + _LOGD("get-bridge-vlan: recv failed: %s (%d)", nm_strerror(nle), nle); + ret = FALSE; + goto err; + } + + if (data.vlans) { + NM_SET_OUT(out_vlans, &nm_g_array_index(data.vlans, NMPlatformBridgeVlan, 0)); + NM_SET_OUT(out_num_vlans, data.vlans->len); + } else { + NM_SET_OUT(out_vlans, NULL); + NM_SET_OUT(out_num_vlans, 0); + } + + if (data.vlans) + g_array_free(data.vlans, !out_vlans); + + ret = TRUE; +err: + if (sk) + nl_socket_free(sk); + return ret; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + static gboolean link_set_bridge_info(NMPlatform *platform, int ifindex, @@ -10965,6 +11104,20 @@ event_handler_read_netlink(NMPlatform *platform, } _reason; })); + + if (nle == -ENOBUFS) { + /* Netlink notifications are coming faster than what + * we can process them. Backoff a bit so we give some + * time for this burst to finish, and we don't + * contribute to starve the system contending for the + * kernel's RTNL lock. + */ + _LOGI("netlink[%s]: backoff for %d seconds before the resync.", + nmp_netlink_protocol_info(netlink_protocol)->name, + RESYNC_BACKOFF_SECONDS); + sleep(RESYNC_BACKOFF_SECONDS); + } + _netlink_recv_handle(platform, netlink_protocol, FALSE); delayed_action_wait_for_nl_response_complete_all( platform, @@ -11809,6 +11962,7 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass) platform_class->link_set_sriov_params_async = link_set_sriov_params_async; platform_class->link_set_sriov_vfs = link_set_sriov_vfs; platform_class->link_set_bridge_vlans = link_set_bridge_vlans; + platform_class->link_get_bridge_vlans = link_get_bridge_vlans; platform_class->link_set_bridge_info = link_set_bridge_info; platform_class->link_get_physical_port_id = link_get_physical_port_id; diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c index 6f3ad05c..3f70f5fe 100644 --- a/src/libnm-platform/nm-platform-utils.c +++ b/src/libnm-platform/nm-platform-utils.c @@ -2275,6 +2275,89 @@ nmp_utils_lifetime_get(guint32 timestamp, /*****************************************************************************/ +static int +bridge_vlan_compare(gconstpointer a, gconstpointer b, gpointer user_data) +{ + const NMPlatformBridgeVlan *vlan_a = a; + const NMPlatformBridgeVlan *vlan_b = b; + + return (int) vlan_a->vid_start - (int) vlan_b->vid_start; +} + +/** + * nmp_utils_bridge_vlan_normalize: + * @vlans: the array of VLAN ranges + * @num_vlans: the number of VLAN ranges in the array. On return, it contains + * the new number. + * + * Sort the VLAN ranges and merge those that are contiguous or overlapping. It + * must not contain invalid data such as 2 overlapping ranges with different + * flags. + */ +void +nmp_utils_bridge_vlan_normalize(NMPlatformBridgeVlan *vlans, guint *num_vlans) +{ + guint i; + + if (*num_vlans <= 1) + return; + + g_qsort_with_data(vlans, *num_vlans, sizeof(NMPlatformBridgeVlan), bridge_vlan_compare, NULL); + + /* Merge VLAN ranges that are contiguous or overlap */ + i = 0; + while (i < *num_vlans - 1) { + guint j = i + 1; + gboolean can_merge = vlans[j].vid_start <= vlans[i].vid_end + 1 + && vlans[j].pvid == vlans[i].pvid + && vlans[j].untagged == vlans[i].untagged; + + if (can_merge) { + vlans[i].vid_end = NM_MAX(vlans[i].vid_end, vlans[j].vid_end); + for (; j < *num_vlans - 1; j++) + vlans[j] = vlans[j + 1]; + *num_vlans -= 1; + } else { + i++; + } + } +} + +/** + * nmp_utils_bridge_normalized_vlans_equal: + * @vlans_a: the first array of bridge VLANs + * @num_vlans_a: the number of elements of first array + * @vlans_b: the second array of bridge VLANs + * @num_vlans_b: the number of elements of second array + * + * Given two arrays of bridge VLAN ranges, compare if they are equal, + * i.e. if they represent the same set of VLANs with the same attributes. + * The input arrays must be normalized (sorted and without overlapping or + * duplicated ranges). Normalize with nmp_utils_bridge_vlan_normalize(). + */ +gboolean +nmp_utils_bridge_normalized_vlans_equal(const NMPlatformBridgeVlan *vlans_a, + guint num_vlans_a, + const NMPlatformBridgeVlan *vlans_b, + guint num_vlans_b) +{ + guint i; + + if (num_vlans_a != num_vlans_b) + return FALSE; + + for (i = 0; i < num_vlans_a; i++) { + if (vlans_a[i].vid_start != vlans_b[i].vid_start || vlans_a[i].vid_end != vlans_b[i].vid_end + || vlans_a[i].pvid != vlans_b[i].pvid || vlans_a[i].untagged != vlans_b[i].untagged) { + return FALSE; + } + } + + return TRUE; +} + +/*****************************************************************************/ + static const char * _trunk_first_line(char *str) { diff --git a/src/libnm-platform/nm-platform-utils.h b/src/libnm-platform/nm-platform-utils.h index 18fc6155..96ac22ef 100644 --- a/src/libnm-platform/nm-platform-utils.h +++ b/src/libnm-platform/nm-platform-utils.h @@ -99,4 +99,11 @@ guint32 nmp_utils_lifetime_get(guint32 timestamp, int nmp_utils_modprobe(GError **error, gboolean suppress_error_logging, const char *arg1, ...) G_GNUC_NULL_TERMINATED; +void nmp_utils_bridge_vlan_normalize(NMPlatformBridgeVlan *vlans, guint *num_vlans); + +gboolean nmp_utils_bridge_normalized_vlans_equal(const NMPlatformBridgeVlan *vlans_a, + guint num_vlans_a, + const NMPlatformBridgeVlan *vlans_b, + guint num_vlans_b); + #endif /* __NM_PLATFORM_UTILS_H__ */ diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index f82de7f9..af04f29f 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -2070,10 +2070,11 @@ nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF } gboolean -nm_platform_link_set_bridge_vlans(NMPlatform *self, - int ifindex, - gboolean on_controller, - const NMPlatformBridgeVlan *const *vlans) +nm_platform_link_set_bridge_vlans(NMPlatform *self, + int ifindex, + gboolean on_controller, + const NMPlatformBridgeVlan *vlans, + guint num_vlans) { guint i; _CHECK_SELF(self, klass, FALSE); @@ -2085,9 +2086,9 @@ nm_platform_link_set_bridge_vlans(NMPlatform *self, vlans ? "setting" : "clearing", on_controller ? "controller" : "self"); if (vlans) { - for (i = 0; vlans[i]; i++) { + for (i = 0; i < num_vlans; i++) { char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; - const NMPlatformBridgeVlan *vlan = vlans[i]; + const NMPlatformBridgeVlan *vlan = &vlans[i]; _LOG3D("link: bridge VLAN %s", nm_platform_bridge_vlan_to_string(vlan, sbuf, sizeof(sbuf))); @@ -2095,7 +2096,41 @@ nm_platform_link_set_bridge_vlans(NMPlatform *self, } } - return klass->link_set_bridge_vlans(self, ifindex, on_controller, vlans); + return klass->link_set_bridge_vlans(self, ifindex, on_controller, vlans, num_vlans); +} + +gboolean +nm_platform_link_get_bridge_vlans(NMPlatform *self, + int ifindex, + NMPlatformBridgeVlan **out_vlans, + guint *out_num_vlans) +{ + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + gboolean ret; + guint i; + + _CHECK_SELF(self, klass, FALSE); + + g_return_val_if_fail(ifindex > 0, FALSE); + g_return_val_if_fail(out_vlans, FALSE); + g_return_val_if_fail(out_num_vlans, FALSE); + + _LOG3D("link: getting bridge VLANs"); + + ret = klass->link_get_bridge_vlans(self, ifindex, out_vlans, out_num_vlans); + + if (_LOGD_ENABLED()) { + if (!ret) { + _LOG3D("link: failure while getting bridge vlans"); + } else { + for (i = 0; i < *out_num_vlans; i++) { + _LOG3D("link: bridge VLAN %s", + nm_platform_bridge_vlan_to_string(&(*out_vlans)[i], sbuf, sizeof(sbuf))); + } + } + } + + return ret; } gboolean diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index b05b1297..e33be813 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -741,13 +741,6 @@ typedef struct { gint8 trust; } NMPlatformVF; -typedef struct { - guint16 vid_start; - guint16 vid_end; - bool untagged : 1; - bool pvid : 1; -} NMPlatformBridgeVlan; - typedef struct { guint16 vlan_default_pvid_val; bool vlan_filtering_val : 1; @@ -1185,10 +1178,15 @@ typedef struct { gpointer callback_data, GCancellable *cancellable); gboolean (*link_set_sriov_vfs)(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs); - gboolean (*link_set_bridge_vlans)(NMPlatform *self, - int ifindex, - gboolean on_controller, - const NMPlatformBridgeVlan *const *vlans); + gboolean (*link_set_bridge_vlans)(NMPlatform *self, + int ifindex, + gboolean on_controller, + const NMPlatformBridgeVlan *vlans, + guint num_vlans); + gboolean (*link_get_bridge_vlans)(NMPlatform *self, + int ifindex, + NMPlatformBridgeVlan **out_vlans, + guint *out_num_vlans); gboolean (*link_set_bridge_info)(NMPlatform *self, int ifindex, const NMPlatformLinkSetBridgeInfoData *bridge_info); @@ -2049,10 +2047,15 @@ void nm_platform_link_set_sriov_params_async(NMPlatform *self, gboolean nm_platform_link_set_sriov_vfs(NMPlatform *self, int ifindex, const NMPlatformVF *const *vfs); -gboolean nm_platform_link_set_bridge_vlans(NMPlatform *self, - int ifindex, - gboolean on_controller, - const NMPlatformBridgeVlan *const *vlans); +gboolean nm_platform_link_set_bridge_vlans(NMPlatform *self, + int ifindex, + gboolean on_controller, + const NMPlatformBridgeVlan *vlans, + guint num_vlans); +gboolean nm_platform_link_get_bridge_vlans(NMPlatform *self, + int ifindex, + NMPlatformBridgeVlan **out_vlans, + guint *out_num_vlans); gboolean nm_platform_link_set_bridge_info(NMPlatform *self, int ifindex, const NMPlatformLinkSetBridgeInfoData *bridge_info); diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h index 70b5d1bc..c7d487e2 100644 --- a/src/libnm-platform/nmp-base.h +++ b/src/libnm-platform/nmp-base.h @@ -38,6 +38,15 @@ typedef enum { /*****************************************************************************/ +typedef struct { + guint16 vid_start; + guint16 vid_end; + bool untagged : 1; + bool pvid : 1; +} NMPlatformBridgeVlan; + +/*****************************************************************************/ + typedef struct { /* We don't want to include in header files, * thus create a ABI compatible version of struct ethtool_drvinfo.*/ diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c index 5fc8a5dd..37707875 100644 --- a/src/libnm-platform/tests/test-nm-platform.c +++ b/src/libnm-platform/tests/test-nm-platform.c @@ -190,6 +190,239 @@ test_nmp_link_mode_all_advertised_modes_bits(void) /*****************************************************************************/ +static void +test_nmp_utils_bridge_vlans_normalize(void) +{ + NMPlatformBridgeVlan vlans[10]; + NMPlatformBridgeVlan expect[10]; + guint vlans_len; + + /* Single one is unmodified */ + vlans[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + expect[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + vlans_len = 1; + nmp_utils_bridge_vlan_normalize(vlans, &vlans_len); + g_assert(vlans_len == 1); + g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len)); + + /* Not merged if flags are different */ + vlans[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + vlans[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 11, + .pvid = TRUE, + }; + vlans[2] = (NMPlatformBridgeVlan){ + .vid_start = 20, + .vid_end = 25, + }; + vlans[3] = (NMPlatformBridgeVlan){ + .vid_start = 26, + .vid_end = 30, + .untagged = TRUE, + }; + vlans[4] = (NMPlatformBridgeVlan){ + .vid_start = 40, + .vid_end = 40, + .untagged = TRUE, + }; + vlans[5] = (NMPlatformBridgeVlan){ + .vid_start = 40, + .vid_end = 40, + .untagged = TRUE, + .pvid = TRUE, + }; + expect[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + expect[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 11, + .pvid = TRUE, + }; + expect[2] = (NMPlatformBridgeVlan){ + .vid_start = 20, + .vid_end = 25, + }; + expect[3] = (NMPlatformBridgeVlan){ + .vid_start = 26, + .vid_end = 30, + .untagged = TRUE, + }; + expect[4] = (NMPlatformBridgeVlan){ + .vid_start = 40, + .vid_end = 40, + .untagged = TRUE, + }; + expect[5] = (NMPlatformBridgeVlan){ + .vid_start = 40, + .vid_end = 40, + .untagged = TRUE, + .pvid = TRUE, + }; + vlans_len = 6; + nmp_utils_bridge_vlan_normalize(vlans, &vlans_len); + g_assert(vlans_len == 6); + g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len)); + + /* Overlapping and contiguous ranges are merged */ + vlans[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + vlans[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 20, + .untagged = TRUE, + }; + vlans[2] = (NMPlatformBridgeVlan){ + .vid_start = 19, + .vid_end = 30, + .untagged = TRUE, + }; + expect[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 30, + .untagged = TRUE, + }; + vlans_len = 3; + nmp_utils_bridge_vlan_normalize(vlans, &vlans_len); + g_assert(vlans_len == 1); + g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len)); + + vlans[0] = (NMPlatformBridgeVlan){ + .vid_start = 20, + .vid_end = 20, + }; + vlans[1] = (NMPlatformBridgeVlan){ + .vid_start = 4, + .vid_end = 4, + .pvid = TRUE, + }; + vlans[2] = (NMPlatformBridgeVlan){ + .vid_start = 33, + .vid_end = 33, + }; + vlans[3] = (NMPlatformBridgeVlan){ + .vid_start = 100, + .vid_end = 100, + .untagged = TRUE, + }; + vlans[4] = (NMPlatformBridgeVlan){ + .vid_start = 34, + .vid_end = 40, + }; + vlans[5] = (NMPlatformBridgeVlan){ + .vid_start = 21, + .vid_end = 32, + }; + expect[0] = (NMPlatformBridgeVlan){ + .vid_start = 4, + .vid_end = 4, + .pvid = TRUE, + }; + expect[1] = (NMPlatformBridgeVlan){ + .vid_start = 20, + .vid_end = 40, + }; + expect[2] = (NMPlatformBridgeVlan){ + .vid_start = 100, + .vid_end = 100, + .untagged = TRUE, + }; + vlans_len = 6; + nmp_utils_bridge_vlan_normalize(vlans, &vlans_len); + g_assert(vlans_len == 3); + g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len)); +} + +static void +test_nmp_utils_bridge_normalized_vlans_equal(void) +{ + NMPlatformBridgeVlan a[10]; + NMPlatformBridgeVlan b[10]; + + /* Both empty */ + g_assert(nmp_utils_bridge_normalized_vlans_equal(NULL, 0, NULL, 0)); + g_assert(nmp_utils_bridge_normalized_vlans_equal(a, 0, b, 0)); + g_assert(nmp_utils_bridge_normalized_vlans_equal(a, 0, NULL, 0)); + g_assert(nmp_utils_bridge_normalized_vlans_equal(NULL, 0, b, 0)); + + /* One empty, other not */ + a[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + g_assert(!nmp_utils_bridge_normalized_vlans_equal(a, 1, NULL, 0)); + g_assert(!nmp_utils_bridge_normalized_vlans_equal(NULL, 0, a, 1)); + + /* Equal range + VLAN */ + a[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + a[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 11, + .pvid = TRUE, + }; + b[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 10, + .untagged = TRUE, + }; + b[1] = (NMPlatformBridgeVlan){ + .vid_start = 11, + .vid_end = 11, + .pvid = TRUE, + }; + g_assert(nmp_utils_bridge_normalized_vlans_equal(a, 2, b, 2)); + g_assert(nmp_utils_bridge_normalized_vlans_equal(b, 2, a, 2)); + + /* Different flag */ + b[1].pvid = FALSE; + g_assert(!nmp_utils_bridge_normalized_vlans_equal(a, 2, b, 2)); + g_assert(!nmp_utils_bridge_normalized_vlans_equal(b, 2, a, 2)); + + /* Different ranges */ + a[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 30, + .untagged = TRUE, + }; + b[0] = (NMPlatformBridgeVlan){ + .vid_start = 1, + .vid_end = 29, + .untagged = TRUE, + }; + g_assert(!nmp_utils_bridge_normalized_vlans_equal(a, 1, b, 1)); + g_assert(!nmp_utils_bridge_normalized_vlans_equal(b, 1, a, 1)); + + b[0].vid_start = 2; + b[0].vid_end = 30; + g_assert(!nmp_utils_bridge_normalized_vlans_equal(a, 1, b, 1)); + g_assert(!nmp_utils_bridge_normalized_vlans_equal(b, 1, a, 1)); +} + +/*****************************************************************************/ + static void test_nmpclass_consistency(void) { @@ -252,6 +485,10 @@ main(int argc, char **argv) g_test_add_func("/nm-platform/test_nmp_link_mode_all_advertised_modes_bits", test_nmp_link_mode_all_advertised_modes_bits); g_test_add_func("/nm-platform/test_nmpclass_consistency", test_nmpclass_consistency); + g_test_add_func("/nm-platform/test_nmp_utils_bridge_vlans_normalize", + test_nmp_utils_bridge_vlans_normalize); + g_test_add_func("/nm-platform/nmp-utils-bridge-vlans-equal", + test_nmp_utils_bridge_normalized_vlans_equal); return g_test_run(); } diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c index 9eac7b9a..7e192388 100644 --- a/src/nmcli/connections.c +++ b/src/nmcli/connections.c @@ -6576,8 +6576,15 @@ extract_setting_and_property(const char *prompt, const char *line, char **settin p2 = dot + 1; num1 = strcspn(p1, "."); num2 = len > num1 + 1 ? len - num1 - 1 : 0; - sett = num1 > 0 ? g_strndup(p1, num1) : sett; - prop = num2 > 0 ? g_strndup(p2, num2) : prop; + if (num1 > 0) { + g_free(sett); + sett = g_strndup(p1, num1); + } + + if (num2 > 0) { + g_free(prop); + prop = g_strndup(p2, num2); + } } else { if (!prop) prop = len > 0 ? g_strndup(p1, len) : NULL; -- cgit 1.3.0-6-gf8a5