summary refs log tree commit diff
path: root/src/libnm-platform/nm-linux-platform.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-platform/nm-linux-platform.c')
-rw-r--r--src/libnm-platform/nm-linux-platform.c172
1 files changed, 163 insertions, 9 deletions
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;