From 557b4721c00f2415d6c1c0b3bb995dd8ea97f868 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 25 Jul 2024 18:32:24 +0200 Subject: [PATCH] device: support reapplying bridge-port VLANs For now, always reapply the VLANs unconditionally, even if they didn't change in kernel. To set again the VLANs on the port we need to clear all the existing one before. However, this deletes also the VLAN for the default-pvid on the bridge. Therefore, we need some additional logic to inject the default-pvid in the list of VLANs. --- NEWS | 1 + src/core/devices/nm-device-bridge.c | 128 ++++++++++++++++++++++++++++ src/core/devices/nm-device-bridge.h | 2 + src/core/devices/nm-device.c | 2 + 4 files changed, 133 insertions(+) diff --git a/NEWS b/NEWS index 720370ee57..daf62fd5f7 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,7 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! * ndisc: Support multiple gateways for a single network * wifi: Support configuring channel-width in AP mode * keyfile: Stop writing offensive terms into keyfiles +* Support reapplying the VLANs on bridge ports. ============================================= NetworkManager-1.48 diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c index cde94a4264..639aa8bae8 100644 --- a/src/core/devices/nm-device-bridge.c +++ b/src/core/devices/nm-device-bridge.c @@ -728,6 +728,134 @@ 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 (has_pvid && vlan) { + /* The port already contains the VLAN ID and has a PVID set: nothing to do */ + } else if (vlan) { + /* The port already contains the VLAN ID but without the PVID flag: set the PVID flag */ + + if (vlan->vid_start == vlan->vid_end) { + /* If it's a single VLAN, just set PVID flag. */ + vlan->pvid = TRUE; + } else { + NMPlatformBridgeVlan old_range = *vlan; + + /* Replace the range with the PVID VLAN and append + * the remaining VLAN ranges at the end. */ + *vlan = (NMPlatformBridgeVlan){ + .vid_start = default_pvid, + .vid_end = default_pvid, + .untagged = TRUE, + .pvid = TRUE, + }; + + vlans = g_realloc_n(vlans, *num_vlans + 2, sizeof(NMPlatformBridgeVlan)); + + if (default_pvid != old_range.vid_start) { + (*num_vlans)++; + vlans[*num_vlans - 1] = (NMPlatformBridgeVlan){ + .vid_start = old_range.vid_start, + .vid_end = default_pvid - 1, + .untagged = old_range.untagged, + .pvid = FALSE, + }; + } + if (default_pvid != old_range.vid_end) { + (*num_vlans)++; + vlans[*num_vlans - 1] = (NMPlatformBridgeVlan){ + .vid_start = default_pvid + 1, + .vid_end = old_range.vid_end, + .untagged = old_range.untagged, + .pvid = FALSE, + }; + } + } + } else { + /* 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) +{ + NMSettingBridgePort *s_bridge_port; + NMDevice *controller; + NMSettingBridge *s_bridge; + gs_unref_ptrarray GPtrArray *tmp_vlans = NULL; + gs_free NMPlatformBridgeVlan *setting_vlans = NULL; + guint num_setting_vlans = 0; + NMPlatform *plat; + int ifindex; + + 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); + + 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) { diff --git a/src/core/devices/nm-device-bridge.h b/src/core/devices/nm-device-bridge.h index 6d9f1614c8..f9be7580bb 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 2382f6588f..82141ce005 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(