about summary refs log tree commit diff
path: root/src/core/devices/nm-device-bridge.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices/nm-device-bridge.c')
-rw-r--r--src/core/devices/nm-device-bridge.c180
1 files changed, 149 insertions, 31 deletions
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;
         }