about summary refs log tree commit diff
path: root/src/libnm-platform/nm-linux-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2022-10-04 00:56:46 +0200
committerMichael Biebl <biebl@debian.org>2022-10-04 00:56:46 +0200
commit3612e6355f3f40d264ec4657547761069baa5430 (patch)
tree768e4f5b37b66f540dd7030aa2eaff2ac7bb0168 /src/libnm-platform/nm-linux-platform.c
parent7e9f00a3be8f65535e6620b344443ec4d403212b (diff)
parent6f7fcde10e47d8af865af336becf1fd8a446e62e (diff)
Merge tag 'debian/1.40.0-1' into debian/bullseye-backports
network-manager Debian release 1.40.0-1
Diffstat (limited to 'src/libnm-platform/nm-linux-platform.c')
-rw-r--r--src/libnm-platform/nm-linux-platform.c2328
1 files changed, 1598 insertions, 730 deletions
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index 40cb4396..cd685fe2 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -7,12 +7,13 @@
 
 #include "nm-linux-platform.h"
 
+#include "libnm-std-aux/nm-linux-compat.h"
+
 #include <arpa/inet.h>
 #include <dlfcn.h>
 #include <endian.h>
 #include <fcntl.h>
 #include <libudev.h>
-#include <net/ethernet.h>
 #include <linux/fib_rules.h>
 #include <linux/ip.h>
 #include <linux/if.h>
@@ -50,6 +51,14 @@
 
 /*****************************************************************************/
 
+G_STATIC_ASSERT(NM_MPTCP_PM_ADDR_FLAG_SIGNAL == MPTCP_PM_ADDR_FLAG_SIGNAL);
+G_STATIC_ASSERT(NM_MPTCP_PM_ADDR_FLAG_SUBFLOW == MPTCP_PM_ADDR_FLAG_SUBFLOW);
+G_STATIC_ASSERT(NM_MPTCP_PM_ADDR_FLAG_BACKUP == MPTCP_PM_ADDR_FLAG_BACKUP);
+G_STATIC_ASSERT(NM_MPTCP_PM_ADDR_FLAG_FULLMESH == MPTCP_PM_ADDR_FLAG_FULLMESH);
+G_STATIC_ASSERT(NM_MPTCP_PM_ADDR_FLAG_IMPLICIT == MPTCP_PM_ADDR_FLAG_IMPLICIT);
+
+/*****************************************************************************/
+
 /* re-implement <linux/tc_act/tc_defact.h> to build against kernel
  * headers that lack this. */
 
@@ -79,6 +88,25 @@ enum { TCA_DEF_UNSPEC, TCA_DEF_TM, TCA_DEF_PARMS, TCA_DEF_DATA, TCA_DEF_PAD, __T
 
 /*****************************************************************************/
 
+typedef enum _nm_packed {
+    _NMP_NETLINK_FIRST  = 0,
+    NMP_NETLINK_GENERIC = 0,
+    NMP_NETLINK_ROUTE,
+    _NMP_NETLINK_NUM,
+    _NMP_NETLINK_NONE = _NMP_NETLINK_NUM,
+} NMPNetlinkProtocol;
+
+#define nmp_netlink_protocol_check(netlink_protocol)                                       \
+    ({                                                                                     \
+        const NMPNetlinkProtocol _netlink_protocol_2 = (netlink_protocol);                 \
+                                                                                           \
+        nm_assert(NM_IN_SET(_netlink_protocol_2, NMP_NETLINK_ROUTE, NMP_NETLINK_GENERIC)); \
+                                                                                           \
+        _netlink_protocol_2;                                                               \
+    })
+
+/*****************************************************************************/
+
 #ifndef IFLA_PROMISCUITY
 #define IFLA_PROMISCUITY 30
 #endif
@@ -149,6 +177,12 @@ G_STATIC_ASSERT(RTA_MAX == (__RTA_MAX - 1));
 
 /*****************************************************************************/
 
+#define IFLA_BOND_PEER_NOTIF_DELAY 28
+
+#undef IFLA_BOND_MAX
+
+/*****************************************************************************/
+
 /* Appeared in the kernel prior to 3.13 dated 19 January, 2014 */
 #ifndef ARPHRD_6LOWPAN
 #define ARPHRD_6LOWPAN 825
@@ -279,6 +313,12 @@ struct _ifla_vf_vlan_info {
 
 /*****************************************************************************/
 
+typedef struct {
+    guint16 family_id;
+} GenlFamilyData;
+
+/*****************************************************************************/
+
 typedef enum {
     INFINIBAND_ACTION_CREATE_CHILD,
     INFINIBAND_ACTION_DELETE_CHILD,
@@ -302,15 +342,17 @@ typedef struct {
 typedef enum {
     _REFRESH_ALL_TYPE_FIRST = 0,
 
-    REFRESH_ALL_TYPE_LINKS             = 0,
-    REFRESH_ALL_TYPE_IP4_ADDRESSES     = 1,
-    REFRESH_ALL_TYPE_IP6_ADDRESSES     = 2,
-    REFRESH_ALL_TYPE_IP4_ROUTES        = 3,
-    REFRESH_ALL_TYPE_IP6_ROUTES        = 4,
-    REFRESH_ALL_TYPE_ROUTING_RULES_IP4 = 5,
-    REFRESH_ALL_TYPE_ROUTING_RULES_IP6 = 6,
-    REFRESH_ALL_TYPE_QDISCS            = 7,
-    REFRESH_ALL_TYPE_TFILTERS          = 8,
+    REFRESH_ALL_TYPE_RTNL_LINKS             = 0,
+    REFRESH_ALL_TYPE_RTNL_IP4_ADDRESSES     = 1,
+    REFRESH_ALL_TYPE_RTNL_IP6_ADDRESSES     = 2,
+    REFRESH_ALL_TYPE_RTNL_IP4_ROUTES        = 3,
+    REFRESH_ALL_TYPE_RTNL_IP6_ROUTES        = 4,
+    REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP4 = 5,
+    REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP6 = 6,
+    REFRESH_ALL_TYPE_RTNL_QDISCS            = 7,
+    REFRESH_ALL_TYPE_RTNL_TFILTERS          = 8,
+
+    REFRESH_ALL_TYPE_GENL_FAMILIES = 9,
 
     _REFRESH_ALL_TYPE_NUM,
 } RefreshAllType;
@@ -318,66 +360,79 @@ typedef enum {
 typedef struct {
     NMPObjectType obj_type;
 
-    /* for NLM_F_DUMP, which address family to request. */
-    int addr_family;
+    NMPNetlinkProtocol protocol;
+
+    /* For NLM_F_DUMP, which address family to request.
+     * Either AF_UNSPEC, AF_INET or AF_INET6. */
+    gint8 addr_family_for_dump;
 } RefreshAllInfo;
 
-typedef enum {
+typedef enum _nm_packed {
     DELAYED_ACTION_TYPE_NONE = 0,
 
 #define F(val, name) ((sizeof(char[(((val)) == (name)) ? 1 : -1]) * 0) + (val))
-    DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS             = 1 << F(0, REFRESH_ALL_TYPE_LINKS),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES     = 1 << F(1, REFRESH_ALL_TYPE_IP4_ADDRESSES),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES     = 1 << F(2, REFRESH_ALL_TYPE_IP6_ADDRESSES),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES        = 1 << F(3, REFRESH_ALL_TYPE_IP4_ROUTES),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES        = 1 << F(4, REFRESH_ALL_TYPE_IP6_ROUTES),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4 = 1
-                                                        << F(5, REFRESH_ALL_TYPE_ROUTING_RULES_IP4),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6 = 1
-                                                        << F(6, REFRESH_ALL_TYPE_ROUTING_RULES_IP6),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS   = 1 << F(7, REFRESH_ALL_TYPE_QDISCS),
-    DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS = 1 << F(8, REFRESH_ALL_TYPE_TFILTERS),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_LINKS = 1 << F(0, REFRESH_ALL_TYPE_RTNL_LINKS),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ADDRESSES =
+        1 << F(1, REFRESH_ALL_TYPE_RTNL_IP4_ADDRESSES),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ADDRESSES =
+        1 << F(2, REFRESH_ALL_TYPE_RTNL_IP6_ADDRESSES),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ROUTES = 1 << F(3, REFRESH_ALL_TYPE_RTNL_IP4_ROUTES),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ROUTES = 1 << F(4, REFRESH_ALL_TYPE_RTNL_IP6_ROUTES),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_IP4 =
+        1 << F(5, REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP4),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_IP6 =
+        1 << F(6, REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP6),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_QDISCS   = 1 << F(7, REFRESH_ALL_TYPE_RTNL_QDISCS),
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_TFILTERS = 1 << F(8, REFRESH_ALL_TYPE_RTNL_TFILTERS),
+
+    DELAYED_ACTION_TYPE_REFRESH_ALL_GENL_FAMILIES = 1 << F(9, REFRESH_ALL_TYPE_GENL_FAMILIES),
 #undef F
 
-    DELAYED_ACTION_TYPE_REFRESH_LINK         = 1 << 9,
-    DELAYED_ACTION_TYPE_MASTER_CONNECTED     = 1 << 10,
-    DELAYED_ACTION_TYPE_READ_NETLINK         = 1 << 11,
-    DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE = 1 << 12,
+    DELAYED_ACTION_TYPE_READ_RTNL              = 1 << 10,
+    DELAYED_ACTION_TYPE_READ_GENL              = 1 << 11,
+    DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_RTNL = 1 << 12,
+    DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_GENL = 1 << 13,
+    DELAYED_ACTION_TYPE_REFRESH_LINK           = 1 << 14,
+    DELAYED_ACTION_TYPE_MASTER_CONNECTED       = 1 << 15,
 
     __DELAYED_ACTION_TYPE_MAX,
 
-    DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL =
-        DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6,
+    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_ALL =
+        DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_IP4
+        | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_IP6,
 
-    DELAYED_ACTION_TYPE_REFRESH_ALL =
-        DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS
-        | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
+    DELAYED_ACTION_TYPE_REFRESH_RTNL_ALL = DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_LINKS
+                                           | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ADDRESSES
+                                           | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ADDRESSES
+                                           | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ROUTES
+                                           | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ROUTES
+                                           | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_ALL
+                                           | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_QDISCS
+                                           | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_TFILTERS,
+
+    DELAYED_ACTION_TYPE_REFRESH_GENL_ALL = DELAYED_ACTION_TYPE_REFRESH_ALL_GENL_FAMILIES,
 
     DELAYED_ACTION_TYPE_MAX = __DELAYED_ACTION_TYPE_MAX - 1,
 } DelayedActionType;
 
-#define FOR_EACH_DELAYED_ACTION(iflags, flags_all)          \
-    for ((iflags) = (DelayedActionType) 0x1LL; ({           \
-             gboolean _good = FALSE;                        \
-                                                            \
-             nm_assert(nm_utils_is_power_of_two(iflags));   \
-                                                            \
-             while ((iflags) <= DELAYED_ACTION_TYPE_MAX) {  \
-                 if (NM_FLAGS_ANY((flags_all), (iflags))) { \
-                     _good = TRUE;                          \
-                     break;                                 \
-                 }                                          \
-                 (iflags) <<= 1;                            \
-             }                                              \
-             _good;                                         \
-         });                                                \
+#define FOR_EACH_DELAYED_ACTION(iflags, flags_all)                          \
+    for ((iflags) = (DelayedActionType) 0x1LL; ({                           \
+             gboolean _good = FALSE;                                        \
+                                                                            \
+             nm_assert((iflags) == 0 || nm_utils_is_power_of_two(iflags));  \
+                                                                            \
+             while ((iflags) != 0 && (iflags) <= DELAYED_ACTION_TYPE_MAX) { \
+                 if (NM_FLAGS_ANY((flags_all), (iflags))) {                 \
+                     _good = TRUE;                                          \
+                     break;                                                 \
+                 }                                                          \
+                 (iflags) <<= 1;                                            \
+             }                                                              \
+             _good;                                                         \
+         });                                                                \
          (iflags) <<= 1)
 
-typedef enum {
+typedef enum _nm_packed {
     /* Negative values are errors from kernel. Add dummy member to
      * make enum signed. */
     _WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = G_MININT,
@@ -392,40 +447,57 @@ typedef enum {
     WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS,
 } WaitForNlResponseResult;
 
-typedef enum {
+typedef enum _nm_packed {
     DELAYED_ACTION_RESPONSE_TYPE_VOID                    = 0,
     DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS = 1,
     DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET               = 2,
 } DelayedActionWaitForNlResponseType;
 
 typedef struct {
-    guint32                            seq_number;
-    WaitForNlResponseResult            seq_result;
-    DelayedActionWaitForNlResponseType response_type;
-    gint64                             timeout_abs_nsec;
-    WaitForNlResponseResult           *out_seq_result;
-    char                             **out_errmsg;
+    WaitForNlResponseResult *out_seq_result;
+    char                   **out_errmsg;
     union {
         int        *out_refresh_all_in_progress;
         NMPObject **out_route_get;
         gpointer    out_data;
     } response;
+    gint64                             timeout_abs_nsec;
+    guint32                            seq_number;
+    WaitForNlResponseResult            seq_result;
+    DelayedActionWaitForNlResponseType response_type;
 } DelayedActionWaitForNlResponseData;
 
 /*****************************************************************************/
 
 typedef struct {
-    struct nl_sock *genl;
-
-    struct nl_sock *nlh;
-
-    GSource *event_source;
-
     guint32 nlh_seq_next;
+    guint32 nlh_seq_last_seen;
 #if NM_MORE_LOGGING
     guint32 nlh_seq_last_handled;
 #endif
-    guint32 nlh_seq_last_seen;
+} NetlinkProtocolPrivData;
+
+typedef struct {
+    struct nl_sock *sk_genl_sync;
+
+    union {
+        struct {
+            struct nl_sock *sk_genl;
+            struct nl_sock *sk_rtnl;
+        };
+        struct nl_sock *sk_x[_NMP_NETLINK_NUM];
+    };
+
+    GSource *event_source_genl;
+    GSource *event_source_rtnl;
+
+    union {
+        struct {
+            NetlinkProtocolPrivData proto_data_genl;
+            NetlinkProtocolPrivData proto_data_rtnl;
+        };
+        NetlinkProtocolPrivData proto_data_x[_NMP_NETLINK_NUM];
+    };
 
     guint32 pruning[_REFRESH_ALL_TYPE_NUM];
 
@@ -446,7 +518,13 @@ typedef struct {
 
         GPtrArray *list_master_connected;
         GPtrArray *list_refresh_link;
-        GArray    *list_wait_for_nl_response;
+        union {
+            struct {
+                GArray *list_wait_for_response_genl;
+                GArray *list_wait_for_response_rtnl;
+            };
+            GArray *list_wait_for_response_x[_NMP_NETLINK_NUM];
+        };
 
         int is_handling;
     } delayed_action;
@@ -462,6 +540,8 @@ typedef struct {
         gsize          len;
     } netlink_recv_buf;
 
+    GenlFamilyData genl_family_data[_NMP_GENL_FAMILY_TYPE_NUM];
+
 } NMLinuxPlatformPrivate;
 
 struct _NMLinuxPlatform {
@@ -562,7 +642,7 @@ NM_LINUX_PLATFORM_FROM_PRIVATE(NMLinuxPlatformPrivate *priv)
 
 static void
 delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
-static gboolean delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink);
+static gboolean delayed_action_handle_all(NMPlatform *platform);
 static void do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char *name);
 static void do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action_type);
 static void cache_on_change(NMPlatform      *platform,
@@ -570,8 +650,36 @@ static void cache_on_change(NMPlatform      *platform,
                             const NMPObject *obj_old,
                             const NMPObject *obj_new);
 static void cache_prune_all(NMPlatform *platform);
-static gboolean        event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks);
-static struct nl_sock *_genl_sock(NMLinuxPlatform *platform);
+static gboolean event_handler_read_netlink(NMPlatform        *platform,
+                                           NMPNetlinkProtocol netlink_protocol,
+                                           gboolean           wait_for_acks);
+
+/*****************************************************************************/
+
+static const struct {
+    gint8             netlink_protocol;
+    DelayedActionType delayed_action_type_read;
+    DelayedActionType delayed_action_type_wait_for_response;
+    const char        name[5];
+} _nmp_netlink_protocol_infos[_NMP_NETLINK_NUM] = {
+    [NMP_NETLINK_ROUTE] =
+        {
+            .netlink_protocol                      = NETLINK_ROUTE,
+            .name                                  = "rtnl",
+            .delayed_action_type_read              = DELAYED_ACTION_TYPE_READ_RTNL,
+            .delayed_action_type_wait_for_response = DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_RTNL,
+        },
+    [NMP_NETLINK_GENERIC] =
+        {
+            .netlink_protocol                      = NETLINK_GENERIC,
+            .name                                  = "genl",
+            .delayed_action_type_read              = DELAYED_ACTION_TYPE_READ_GENL,
+            .delayed_action_type_wait_for_response = DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_GENL,
+        },
+};
+
+#define nmp_netlink_protocol_info(netlink_protocol) \
+    (&_nmp_netlink_protocol_infos[nmp_netlink_protocol_check((netlink_protocol))])
 
 /*****************************************************************************/
 
@@ -1434,6 +1542,152 @@ _parse_lnk_bridge(const char *kind, struct nlattr *info_data)
 /***********************************************************************************/
 
 static NMPObject *
+_parse_lnk_bond(const char *kind, struct nlattr *info_data)
+{
+    static const struct nla_policy policy[] = {
+        [IFLA_BOND_MODE]              = {.type = NLA_U8},
+        [IFLA_BOND_ACTIVE_SLAVE]      = {.type = NLA_U32},
+        [IFLA_BOND_MIIMON]            = {.type = NLA_U32},
+        [IFLA_BOND_UPDELAY]           = {.type = NLA_U32},
+        [IFLA_BOND_DOWNDELAY]         = {.type = NLA_U32},
+        [IFLA_BOND_USE_CARRIER]       = {.type = NLA_U8},
+        [IFLA_BOND_ARP_INTERVAL]      = {.type = NLA_U32},
+        [IFLA_BOND_ARP_IP_TARGET]     = {.type = NLA_NESTED},
+        [IFLA_BOND_ARP_VALIDATE]      = {.type = NLA_U32},
+        [IFLA_BOND_ARP_ALL_TARGETS]   = {.type = NLA_U32},
+        [IFLA_BOND_PRIMARY]           = {.type = NLA_U32},
+        [IFLA_BOND_PRIMARY_RESELECT]  = {.type = NLA_U8},
+        [IFLA_BOND_FAIL_OVER_MAC]     = {.type = NLA_U8},
+        [IFLA_BOND_XMIT_HASH_POLICY]  = {.type = NLA_U8},
+        [IFLA_BOND_RESEND_IGMP]       = {.type = NLA_U32},
+        [IFLA_BOND_NUM_PEER_NOTIF]    = {.type = NLA_U8},
+        [IFLA_BOND_ALL_SLAVES_ACTIVE] = {.type = NLA_U8},
+        [IFLA_BOND_MIN_LINKS]         = {.type = NLA_U32},
+        [IFLA_BOND_LP_INTERVAL]       = {.type = NLA_U32},
+        [IFLA_BOND_PACKETS_PER_SLAVE] = {.type = NLA_U32},
+        [IFLA_BOND_AD_LACP_RATE]      = {.type = NLA_U8},
+        [IFLA_BOND_AD_SELECT]         = {.type = NLA_U8},
+        [IFLA_BOND_AD_ACTOR_SYS_PRIO] = {.type = NLA_U16},
+        [IFLA_BOND_AD_USER_PORT_KEY]  = {.type = NLA_U16},
+        [IFLA_BOND_AD_ACTOR_SYSTEM]   = {.minlen = sizeof(NMEtherAddr)},
+        [IFLA_BOND_TLB_DYNAMIC_LB]    = {.type = NLA_U8},
+        [IFLA_BOND_PEER_NOTIF_DELAY]  = {.type = NLA_U32},
+    };
+    NMPlatformLnkBond *props;
+    struct nlattr     *tb[G_N_ELEMENTS(policy)];
+    NMPObject         *obj = NULL;
+
+    if (!info_data || !nm_streq0(kind, "bond"))
+        return NULL;
+
+    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
+        return NULL;
+
+    obj = nmp_object_new(NMP_OBJECT_TYPE_LNK_BOND, NULL);
+
+    props = &obj->lnk_bond;
+
+    if (tb[IFLA_BOND_MODE])
+        props->mode = nla_get_u8(tb[IFLA_BOND_MODE]);
+    if (tb[IFLA_BOND_PRIMARY]) {
+        props->primary = nla_get_u32(tb[IFLA_BOND_PRIMARY]);
+    } else if (tb[IFLA_BOND_ACTIVE_SLAVE]) {
+        props->primary = nla_get_u32(tb[IFLA_BOND_ACTIVE_SLAVE]);
+    }
+    if (tb[IFLA_BOND_MIIMON]) {
+        props->miimon     = nla_get_u32(tb[IFLA_BOND_MIIMON]);
+        props->miimon_has = TRUE;
+    } else {
+        props->miimon_has = FALSE;
+    }
+    if (tb[IFLA_BOND_UPDELAY]) {
+        props->updelay     = nla_get_u32(tb[IFLA_BOND_UPDELAY]);
+        props->updelay_has = TRUE;
+    } else {
+        props->updelay_has = FALSE;
+    }
+    if (tb[IFLA_BOND_DOWNDELAY]) {
+        props->downdelay     = nla_get_u32(tb[IFLA_BOND_DOWNDELAY]);
+        props->downdelay_has = TRUE;
+    } else {
+        props->downdelay_has = FALSE;
+    }
+    if (tb[IFLA_BOND_USE_CARRIER])
+        props->use_carrier = nla_get_u8(tb[IFLA_BOND_USE_CARRIER]);
+    if (tb[IFLA_BOND_ARP_INTERVAL])
+        props->arp_interval = nla_get_u32(tb[IFLA_BOND_ARP_INTERVAL]);
+    if (tb[IFLA_BOND_ARP_IP_TARGET]) {
+        struct nlattr *attr;
+        int            rem;
+
+        nla_for_each_nested (attr, tb[IFLA_BOND_ARP_IP_TARGET], rem) {
+            if (props->arp_ip_targets_num > NM_BOND_MAX_ARP_TARGETS - 1)
+                break;
+            if (nla_len(attr) < sizeof(in_addr_t))
+                break;
+
+            props->arp_ip_target[props->arp_ip_targets_num++] = nla_get_u32(attr);
+        }
+    }
+    if (tb[IFLA_BOND_ARP_VALIDATE])
+        props->arp_validate = nla_get_u32(tb[IFLA_BOND_ARP_VALIDATE]);
+    if (tb[IFLA_BOND_ARP_ALL_TARGETS]) {
+        props->arp_all_targets     = nla_get_u32(tb[IFLA_BOND_ARP_ALL_TARGETS]);
+        props->arp_all_targets_has = TRUE;
+    } else {
+        props->arp_all_targets_has = FALSE;
+    }
+    if (tb[IFLA_BOND_PRIMARY_RESELECT])
+        props->primary_reselect = nla_get_u8(tb[IFLA_BOND_PRIMARY_RESELECT]);
+    if (tb[IFLA_BOND_FAIL_OVER_MAC])
+        props->fail_over_mac = nla_get_u8(tb[IFLA_BOND_FAIL_OVER_MAC]);
+    if (tb[IFLA_BOND_XMIT_HASH_POLICY])
+        props->xmit_hash_policy = nla_get_u8(tb[IFLA_BOND_XMIT_HASH_POLICY]);
+    if (tb[IFLA_BOND_RESEND_IGMP]) {
+        props->resend_igmp     = nla_get_u32(tb[IFLA_BOND_RESEND_IGMP]);
+        props->resend_igmp_has = TRUE;
+    } else {
+        props->resend_igmp_has = FALSE;
+    }
+    if (tb[IFLA_BOND_NUM_PEER_NOTIF])
+        props->num_grat_arp = nla_get_u8(tb[IFLA_BOND_NUM_PEER_NOTIF]);
+    if (tb[IFLA_BOND_ALL_SLAVES_ACTIVE])
+        props->all_ports_active = nla_get_u8(tb[IFLA_BOND_ALL_SLAVES_ACTIVE]);
+    if (tb[IFLA_BOND_MIN_LINKS])
+        props->min_links = nla_get_u32(tb[IFLA_BOND_MIN_LINKS]);
+    if (tb[IFLA_BOND_LP_INTERVAL])
+        props->lp_interval = nla_get_u32(tb[IFLA_BOND_LP_INTERVAL]);
+    if (tb[IFLA_BOND_PACKETS_PER_SLAVE])
+        props->packets_per_port = nla_get_u32(tb[IFLA_BOND_PACKETS_PER_SLAVE]);
+    if (tb[IFLA_BOND_AD_LACP_RATE])
+        props->lacp_rate = nla_get_u8(tb[IFLA_BOND_AD_LACP_RATE]);
+    if (tb[IFLA_BOND_AD_SELECT])
+        props->ad_select = nla_get_u8(tb[IFLA_BOND_AD_SELECT]);
+    if (tb[IFLA_BOND_AD_ACTOR_SYS_PRIO])
+        props->ad_actor_sys_prio = nla_get_u16(tb[IFLA_BOND_AD_ACTOR_SYS_PRIO]);
+    if (tb[IFLA_BOND_AD_USER_PORT_KEY])
+        props->ad_user_port_key = nla_get_u16(tb[IFLA_BOND_AD_USER_PORT_KEY]);
+    if (tb[IFLA_BOND_AD_ACTOR_SYSTEM])
+        props->ad_actor_system = *nla_data_as(NMEtherAddr, tb[IFLA_BOND_AD_ACTOR_SYSTEM]);
+    if (tb[IFLA_BOND_TLB_DYNAMIC_LB]) {
+        props->tlb_dynamic_lb     = nla_get_u8(tb[IFLA_BOND_TLB_DYNAMIC_LB]);
+        props->tlb_dynamic_lb_has = TRUE;
+    } else {
+        props->tlb_dynamic_lb_has = FALSE;
+    }
+    if (tb[IFLA_BOND_PEER_NOTIF_DELAY]) {
+        props->peer_notif_delay     = nla_get_u32(tb[IFLA_BOND_PEER_NOTIF_DELAY]);
+        props->peer_notif_delay_has = TRUE;
+    } else {
+        props->peer_notif_delay_has = FALSE;
+    }
+
+    return obj;
+}
+
+/***********************************************************************************/
+
+static NMPObject *
 _parse_lnk_gre(const char *kind, struct nlattr *info_data)
 {
     static const struct nla_policy policy[] = {
@@ -2322,7 +2576,7 @@ typedef struct {
 } WireGuardParseData;
 
 static int
-_wireguard_get_device_cb(struct nl_msg *msg, void *arg)
+_wireguard_get_device_cb(const struct nl_msg *msg, void *arg)
 {
     static const struct nla_policy policy[] = {
         [WGDEVICE_A_IFINDEX]     = {.type = NLA_U32},
@@ -2400,7 +2654,7 @@ _wireguard_get_device_cb(struct nl_msg *msg, void *arg)
 static const NMPObject *
 _wireguard_read_info(NMPlatform     *platform /* used only as logging context */,
                      struct nl_sock *genl,
-                     int             wireguard_family_id,
+                     guint16         wireguard_family_id,
                      int             ifindex)
 {
     nm_auto_nlmsg struct nl_msg *msg = NULL;
@@ -2414,10 +2668,12 @@ _wireguard_read_info(NMPlatform     *platform /* used only as logging context */
     guint i;
 
     nm_assert(genl);
-    nm_assert(wireguard_family_id >= 0);
     nm_assert(ifindex > 0);
 
-    _LOGT("wireguard: fetching information for ifindex %d (genl-id %d)...",
+    if (wireguard_family_id == 0)
+        return NULL;
+
+    _LOGT("wireguard: fetching information for ifindex %d (genl-id 0x%x)...",
           ifindex,
           wireguard_family_id);
 
@@ -2530,25 +2786,8 @@ nla_put_failure:
     g_return_val_if_reached(NULL);
 }
 
-static int
-_wireguard_get_family_id(NMPlatform *platform, int ifindex_try)
-{
-    NMLinuxPlatformPrivate *priv                = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    int                     wireguard_family_id = -1;
-
-    if (ifindex_try > 0) {
-        const NMPlatformLink *plink;
-
-        if (nm_platform_link_get_lnk_wireguard(platform, ifindex_try, &plink))
-            wireguard_family_id = NMP_OBJECT_UP_CAST(plink)->_link.wireguard_family_id;
-    }
-    if (wireguard_family_id < 0)
-        wireguard_family_id = genl_ctrl_resolve(priv->genl, "wireguard");
-    return wireguard_family_id;
-}
-
 static const NMPObject *
-_wireguard_refresh_link(NMPlatform *platform, int wireguard_family_id, int ifindex)
+_wireguard_refresh_link(NMPlatform *platform, guint16 wireguard_family_id, int ifindex)
 {
     NMLinuxPlatformPrivate         *priv    = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
     nm_auto_nmpobj const NMPObject *obj_old = NULL;
@@ -2558,7 +2797,7 @@ _wireguard_refresh_link(NMPlatform *platform, int wireguard_family_id, int ifind
     const NMPObject                *plink = NULL;
     nm_auto_nmpobj NMPObject       *obj   = NULL;
 
-    nm_assert(wireguard_family_id >= 0);
+    nm_assert(wireguard_family_id > 0);
     nm_assert(ifindex > 0);
 
     nm_platform_process_events(platform);
@@ -2573,7 +2812,7 @@ _wireguard_refresh_link(NMPlatform *platform, int wireguard_family_id, int ifind
         if (NMP_OBJECT_GET_TYPE(plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)
             lnk_new = nmp_object_ref(plink->_link.netlink.lnk);
     } else {
-        lnk_new = _wireguard_read_info(platform, priv->genl, wireguard_family_id, ifindex);
+        lnk_new = _wireguard_read_info(platform, priv->sk_genl_sync, wireguard_family_id, ifindex);
         if (!lnk_new) {
             if (NMP_OBJECT_GET_TYPE(plink->_link.netlink.lnk) == NMP_OBJECT_TYPE_LNK_WIREGUARD)
                 lnk_new = nmp_object_ref(plink->_link.netlink.lnk);
@@ -2583,15 +2822,13 @@ _wireguard_refresh_link(NMPlatform *platform, int wireguard_family_id, int ifind
         }
     }
 
-    if (plink->_link.wireguard_family_id == wireguard_family_id
-        && plink->_link.netlink.lnk == lnk_new)
+    if (plink->_link.netlink.lnk == lnk_new)
         return plink;
 
     /* we use nmp_cache_update_netlink() to re-inject the new object into the cache.
      * For that, we need to clone it, and tweak it so that it's suitable. It's a bit
      * of a hack, in particular that we need to clear driver and udev-device. */
-    obj                            = nmp_object_clone(plink, FALSE);
-    obj->_link.wireguard_family_id = wireguard_family_id;
+    obj = nmp_object_clone(plink, FALSE);
     nmp_object_unref(obj->_link.netlink.lnk);
     obj->_link.netlink.lnk = g_steal_pointer(&lnk_new);
     obj->link.driver       = NULL;
@@ -2617,7 +2854,7 @@ _wireguard_refresh_link(NMPlatform *platform, int wireguard_family_id, int ifind
 static int
 _wireguard_create_change_nlmsgs(NMPlatform                               *platform,
                                 int                                       ifindex,
-                                int                                       wireguard_family_id,
+                                guint16                                   wireguard_family_id,
                                 const NMPlatformLnkWireGuard             *lnk_wireguard,
                                 const NMPWireGuardPeer                   *peers,
                                 const NMPlatformWireGuardChangePeerFlags *peer_flags,
@@ -2636,14 +2873,6 @@ _wireguard_create_change_nlmsgs(NMPlatform                               *platfo
     struct nlattr                     *nest_curr_allowed_ip;
     NMPlatformWireGuardChangePeerFlags p_flags = NM_PLATFORM_WIREGUARD_CHANGE_PEER_FLAG_DEFAULT;
 
-#define _nla_nest_end(msg, nest_start)             \
-    G_STMT_START                                   \
-    {                                              \
-        if (nla_nest_end((msg), (nest_start)) < 0) \
-            g_return_val_if_reached(-NME_BUG);     \
-    }                                              \
-    G_STMT_END
-
     /* Adapted from LGPL-2.1+ code [1].
      *
      * [1] https://git.zx2c4.com/WireGuard/tree/contrib/examples/embeddable-wg-library/wireguard.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n1073 */
@@ -2799,36 +3028,36 @@ again:
                     if (nla_put_uint8(msg, WGALLOWEDIP_A_CIDR_MASK, aip->mask) < 0)
                         goto toobig_allowedips;
 
-                    _nla_nest_end(msg, nest_curr_allowed_ip);
+                    NLA_NEST_END(msg, nest_curr_allowed_ip);
                     nest_curr_allowed_ip = NULL;
                 }
                 idx_allowed_ips_curr = IDX_NIL;
 
-                _nla_nest_end(msg, nest_allowed_ips);
+                NLA_NEST_END(msg, nest_allowed_ips);
                 nest_allowed_ips = NULL;
             }
         }
 
-        _nla_nest_end(msg, nest_curr_peer);
+        NLA_NEST_END(msg, nest_curr_peer);
         nest_curr_peer = NULL;
     }
 
-    _nla_nest_end(msg, nest_peers);
+    NLA_NEST_END(msg, nest_peers);
     goto send;
 
 toobig_allowedips:
     if (nest_curr_allowed_ip)
         nla_nest_cancel(msg, nest_curr_allowed_ip);
     if (nest_allowed_ips)
-        _nla_nest_end(msg, nest_allowed_ips);
-    _nla_nest_end(msg, nest_curr_peer);
-    _nla_nest_end(msg, nest_peers);
+        NLA_NEST_END(msg, nest_allowed_ips);
+    NLA_NEST_END(msg, nest_curr_peer);
+    NLA_NEST_END(msg, nest_peers);
     goto send;
 
 toobig_peers:
     if (nest_curr_peer)
         nla_nest_cancel(msg, nest_curr_peer);
-    _nla_nest_end(msg, nest_peers);
+    NLA_NEST_END(msg, nest_peers);
     goto send;
 
 send:
@@ -2844,8 +3073,6 @@ send:
 
 nla_put_failure:
     g_return_val_if_reached(-NME_BUG);
-
-#undef _nla_nest_end
 }
 
 static int
@@ -2859,12 +3086,12 @@ link_wireguard_change(NMPlatform                               *platform,
 {
     NMLinuxPlatformPrivate      *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
     gs_unref_ptrarray GPtrArray *msgs = NULL;
-    int                          wireguard_family_id;
+    guint16                      wireguard_family_id;
     guint                        i;
     int                          r;
 
-    wireguard_family_id = _wireguard_get_family_id(platform, ifindex);
-    if (wireguard_family_id < 0)
+    wireguard_family_id = nm_platform_genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_WIREGUARD);
+    if (wireguard_family_id == 0)
         return -NME_PL_NO_FIRMWARE;
 
     r = _wireguard_create_change_nlmsgs(platform,
@@ -2882,14 +3109,14 @@ link_wireguard_change(NMPlatform                               *platform,
     }
 
     for (i = 0; i < msgs->len; i++) {
-        r = nl_send_auto(priv->genl, msgs->pdata[i]);
+        r = nl_send_auto(priv->sk_genl_sync, msgs->pdata[i]);
         if (r < 0) {
             _LOGW("wireguard: set-device, send netlink message #%u failed: %s", i, nm_strerror(r));
             return r;
         }
 
         do {
-            r = nl_recvmsgs(priv->genl, NULL);
+            r = nl_recvmsgs(priv->sk_genl_sync, NULL);
         } while (r == -EAGAIN);
         if (r < 0) {
             _LOGW("wireguard: set-device, message #%u was rejected: %s", i, nm_strerror(r));
@@ -2925,10 +3152,10 @@ _nmp_link_address_set(NMPLinkAddress *dst, const struct nlattr *nla)
 
 /* Copied and heavily modified from libnl3's link_msg_parser(). */
 static NMPObject *
-_new_from_nl_link(NMPlatform      *platform,
-                  const NMPCache  *cache,
-                  struct nlmsghdr *nlh,
-                  gboolean         id_only)
+_new_from_nl_link(NMPlatform            *platform,
+                  const NMPCache        *cache,
+                  const struct nlmsghdr *nlh,
+                  gboolean               id_only)
 {
     static const struct nla_policy policy[] = {
         [IFLA_IFNAME]        = {.type = NLA_STRING, .maxlen = IFNAMSIZ},
@@ -3117,6 +3344,9 @@ _new_from_nl_link(NMPlatform      *platform,
     case NM_LINK_TYPE_BRIDGE:
         lnk_data = _parse_lnk_bridge(nl_info_kind, nl_info_data);
         break;
+    case NM_LINK_TYPE_BOND:
+        lnk_data = _parse_lnk_bond(nl_info_kind, nl_info_data);
+        break;
     case NM_LINK_TYPE_GRE:
     case NM_LINK_TYPE_GRETAP:
         lnk_data = _parse_lnk_gre(nl_info_kind, nl_info_data);
@@ -3220,16 +3450,18 @@ _new_from_nl_link(NMPlatform      *platform,
         switch (obj->link.type) {
         case NM_LINK_TYPE_WIFI:
         case NM_LINK_TYPE_OLPC_MESH:
-            obj->_link.ext_data =
-                (GObject *) nm_wifi_utils_new(ifi->ifi_index,
-                                              _genl_sock(NM_LINUX_PLATFORM(platform)),
-                                              TRUE);
+            obj->_link.ext_data = (GObject *) nm_wifi_utils_new(
+                NM_LINUX_PLATFORM_GET_PRIVATE(platform)->sk_genl_sync,
+                nm_platform_genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_NL80211),
+                ifi->ifi_index,
+                TRUE);
             break;
         case NM_LINK_TYPE_WPAN:
-            obj->_link.ext_data =
-                (GObject *) nm_wpan_utils_new(ifi->ifi_index,
-                                              _genl_sock(NM_LINUX_PLATFORM(platform)),
-                                              TRUE);
+            obj->_link.ext_data = (GObject *) nm_wpan_utils_new(
+                NM_LINUX_PLATFORM_GET_PRIVATE(platform)->sk_genl_sync,
+                nm_platform_genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_NL802154),
+                ifi->ifi_index,
+                TRUE);
             break;
         default:
             g_assert_not_reached();
@@ -3238,28 +3470,17 @@ _new_from_nl_link(NMPlatform      *platform,
 
     if (obj->link.type == NM_LINK_TYPE_WIREGUARD) {
         const NMPObject *lnk_data_new = NULL;
-        struct nl_sock  *genl         = NM_LINUX_PLATFORM_GET_PRIVATE(platform)->genl;
+        struct nl_sock  *genl         = NM_LINUX_PLATFORM_GET_PRIVATE(platform)->sk_genl_sync;
 
         /* The WireGuard kernel module does not yet send link update
          * notifications, so we don't actually update the cache. For
          * now, always refetch link data here. */
 
-        _lookup_cached_link(cache, obj->link.ifindex, completed_from_cache, &link_cached);
-        if (link_cached && link_cached->_link.netlink.is_in_netlink
-            && link_cached->link.type == NM_LINK_TYPE_WIREGUARD)
-            obj->_link.wireguard_family_id = link_cached->_link.wireguard_family_id;
-        else
-            obj->_link.wireguard_family_id = -1;
-
-        if (obj->_link.wireguard_family_id < 0)
-            obj->_link.wireguard_family_id = genl_ctrl_resolve(genl, "wireguard");
-
-        if (obj->_link.wireguard_family_id >= 0) {
-            lnk_data_new = _wireguard_read_info(platform,
-                                                genl,
-                                                obj->_link.wireguard_family_id,
-                                                obj->link.ifindex);
-        }
+        lnk_data_new = _wireguard_read_info(
+            platform,
+            genl,
+            nm_platform_genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_WIREGUARD),
+            obj->link.ifindex);
 
         if (lnk_data_new && obj->_link.netlink.lnk
             && nmp_object_equal(obj->_link.netlink.lnk, lnk_data_new))
@@ -3276,7 +3497,7 @@ _new_from_nl_link(NMPlatform      *platform,
 
 /* Copied and heavily modified from libnl3's addr_msg_parser(). */
 static NMPObject *
-_new_from_nl_addr(struct nlmsghdr *nlh, gboolean id_only)
+_new_from_nl_addr(const struct nlmsghdr *nlh, gboolean id_only)
 {
     static const struct nla_policy policy[] = {
         [IFA_LABEL]     = {.type = NLA_STRING, .maxlen = IFNAMSIZ},
@@ -3391,7 +3612,7 @@ _new_from_nl_addr(struct nlmsghdr *nlh, gboolean id_only)
 
 /* Copied and heavily modified from libnl3's rtnl_route_parse() and parse_multipath(). */
 static NMPObject *
-_new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter *parse_nlmsg_iter)
+_new_from_nl_route(const struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter *parse_nlmsg_iter)
 {
     static const struct nla_policy policy[] = {
         [RTA_TABLE]     = {.type = NLA_U32},
@@ -3420,13 +3641,15 @@ _new_from_nl_route(struct nlmsghdr *nlh, gboolean id_only, ParseNlmsgIter *parse
         .found    = FALSE,
         .has_more = FALSE,
     };
-    guint32 mss;
-    guint32 window   = 0;
-    guint32 cwnd     = 0;
-    guint32 initcwnd = 0;
-    guint32 initrwnd = 0;
-    guint32 mtu      = 0;
-    guint32 lock     = 0;
+    guint32  mss;
+    guint32  window   = 0;
+    guint32  cwnd     = 0;
+    guint32  initcwnd = 0;
+    guint32  initrwnd = 0;
+    guint32  mtu      = 0;
+    guint32  rto_min  = 0;
+    guint32  lock     = 0;
+    gboolean quickack = FALSE;
 
     nm_assert((parse_nlmsg_iter->iter_more && parse_nlmsg_iter->ip6_route.next_multihop > 0)
               || (!parse_nlmsg_iter->iter_more && parse_nlmsg_iter->ip6_route.next_multihop == 0));
@@ -3604,6 +3827,8 @@ rta_multipath_done:
             [RTAX_INITCWND] = {.type = NLA_U32},
             [RTAX_INITRWND] = {.type = NLA_U32},
             [RTAX_MTU]      = {.type = NLA_U32},
+            [RTAX_QUICKACK] = {.type = NLA_U32},
+            [RTAX_RTO_MIN]  = {.type = NLA_U32},
         };
         struct nlattr *mtb[G_N_ELEMENTS(rtax_policy)];
 
@@ -3624,6 +3849,10 @@ rta_multipath_done:
             initrwnd = nla_get_u32(mtb[RTAX_INITRWND]);
         if (mtb[RTAX_MTU])
             mtu = nla_get_u32(mtb[RTAX_MTU]);
+        if (mtb[RTAX_RTO_MIN])
+            rto_min = nla_get_u32(mtb[RTAX_RTO_MIN]);
+        if (mtb[RTAX_QUICKACK])
+            quickack = !!nla_get_u32(mtb[RTAX_QUICKACK]);
     }
 
     /*****************************************************************/
@@ -3674,12 +3903,15 @@ rta_multipath_done:
     obj->ip_route.cwnd          = cwnd;
     obj->ip_route.initcwnd      = initcwnd;
     obj->ip_route.initrwnd      = initrwnd;
+    obj->ip_route.rto_min       = rto_min;
+    obj->ip_route.quickack      = quickack;
     obj->ip_route.mtu           = mtu;
     obj->ip_route.lock_window   = NM_FLAGS_HAS(lock, 1 << RTAX_WINDOW);
     obj->ip_route.lock_cwnd     = NM_FLAGS_HAS(lock, 1 << RTAX_CWND);
     obj->ip_route.lock_initcwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITCWND);
     obj->ip_route.lock_initrwnd = NM_FLAGS_HAS(lock, 1 << RTAX_INITRWND);
     obj->ip_route.lock_mtu      = NM_FLAGS_HAS(lock, 1 << RTAX_MTU);
+    obj->ip_route.lock_mss      = NM_FLAGS_HAS(lock, 1 << RTAX_ADVMSS);
 
     if (!IS_IPv4) {
         if (tb[RTA_PREF])
@@ -3699,7 +3931,7 @@ rta_multipath_done:
 }
 
 static NMPObject *
-_new_from_nl_routing_rule(struct nlmsghdr *nlh, gboolean id_only)
+_new_from_nl_routing_rule(const struct nlmsghdr *nlh, gboolean id_only)
 {
     static const struct nla_policy policy[] = {
         [FRA_UNSPEC] = {},
@@ -3975,7 +4207,7 @@ psched_tick_to_time(NMPlatform *platform, guint32 tick)
 }
 
 static NMPObject *
-_new_from_nl_qdisc(NMPlatform *platform, struct nlmsghdr *nlh, gboolean id_only)
+_new_from_nl_qdisc(NMPlatform *platform, const struct nlmsghdr *nlh, gboolean id_only)
 {
     static const struct nla_policy policy[] = {
         [TCA_KIND]    = {.type = NLA_STRING},
@@ -4088,7 +4320,7 @@ _new_from_nl_qdisc(NMPlatform *platform, struct nlmsghdr *nlh, gboolean id_only)
 }
 
 static NMPObject *
-_new_from_nl_tfilter(NMPlatform *platform, struct nlmsghdr *nlh, gboolean id_only)
+_new_from_nl_tfilter(NMPlatform *platform, const struct nlmsghdr *nlh, gboolean id_only)
 {
     static const struct nla_policy policy[] = {
         [TCA_KIND] = {.type = NLA_STRING},
@@ -4133,18 +4365,17 @@ _new_from_nl_tfilter(NMPlatform *platform, struct nlmsghdr *nlh, gboolean id_onl
  * Returns: %NULL or a newly created NMPObject instance.
  **/
 static NMPObject *
-nmp_object_new_from_nl(NMPlatform     *platform,
-                       const NMPCache *cache,
-                       struct nl_msg  *msg,
-                       gboolean        id_only,
-                       ParseNlmsgIter *parse_nlmsg_iter)
+nmp_object_new_from_nl(NMPlatform               *platform,
+                       const NMPCache           *cache,
+                       const struct nl_msg_lite *msg,
+                       gboolean                  id_only,
+                       ParseNlmsgIter           *parse_nlmsg_iter)
 {
-    struct nlmsghdr *msghdr;
+    const struct nlmsghdr *msghdr;
 
-    if (nlmsg_get_proto(msg) != NETLINK_ROUTE)
-        return NULL;
+    nm_assert(msg->nm_protocol == NETLINK_ROUTE);
 
-    msghdr = nlmsg_hdr(msg);
+    msghdr = msg->nm_nlh;
 
     switch (msghdr->nlmsg_type) {
     case RTM_NEWLINK:
@@ -4268,6 +4499,86 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo
         NLA_PUT_U64(msg, IFLA_BR_MCAST_STARTUP_QUERY_INTVL, props->mcast_startup_query_interval);
         break;
     }
+    case NM_LINK_TYPE_BOND:
+    {
+        const NMPlatformLnkBond *props = extra_data;
+        struct nlattr           *targets;
+        int                      i = 0;
+
+        nm_assert(extra_data);
+
+        if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
+            goto nla_put_failure;
+
+        if (props->arp_ip_targets_num > 0) {
+            targets = nla_nest_start(msg, IFLA_BOND_ARP_IP_TARGET);
+            if (!targets)
+                goto nla_put_failure;
+
+            for (i = 0; i < props->arp_ip_targets_num; i++)
+                NLA_PUT_U32(msg, i, props->arp_ip_target[i]);
+
+            nla_nest_end(msg, targets);
+        }
+
+        if (props->arp_all_targets_has)
+            NLA_PUT_U32(msg, IFLA_BOND_ARP_ALL_TARGETS, props->arp_all_targets);
+        if (props->arp_interval)
+            NLA_PUT_U32(msg, IFLA_BOND_ARP_INTERVAL, props->arp_interval);
+        if (props->arp_validate)
+            NLA_PUT_U32(msg, IFLA_BOND_ARP_VALIDATE, props->arp_validate);
+        if (props->downdelay_has)
+            NLA_PUT_U32(msg, IFLA_BOND_DOWNDELAY, props->downdelay);
+        if (props->lp_interval_has)
+            NLA_PUT_U32(msg, IFLA_BOND_LP_INTERVAL, props->lp_interval);
+        if (props->miimon_has)
+            NLA_PUT_U32(msg, IFLA_BOND_MIIMON, props->miimon);
+        if (props->min_links)
+            NLA_PUT_U32(msg, IFLA_BOND_MIN_LINKS, props->min_links);
+        if (props->packets_per_port)
+            NLA_PUT_U32(msg, IFLA_BOND_PACKETS_PER_SLAVE, props->packets_per_port);
+        if (props->peer_notif_delay_has)
+            NLA_PUT_U32(msg, IFLA_BOND_PEER_NOTIF_DELAY, props->peer_notif_delay);
+        if (props->primary)
+            NLA_PUT_U32(msg, IFLA_BOND_PRIMARY, props->primary);
+        if (props->resend_igmp_has)
+            NLA_PUT_U32(msg, IFLA_BOND_RESEND_IGMP, props->resend_igmp);
+        if (props->updelay_has)
+            NLA_PUT_U32(msg, IFLA_BOND_UPDELAY, props->updelay);
+        if (props->ad_actor_sys_prio)
+            NLA_PUT_U16(msg, IFLA_BOND_AD_ACTOR_SYS_PRIO, props->ad_actor_sys_prio);
+        if (props->ad_user_port_key)
+            NLA_PUT_U16(msg, IFLA_BOND_AD_USER_PORT_KEY, props->ad_user_port_key);
+        if (!nm_ether_addr_equal(&props->ad_actor_system, &nm_ether_addr_zero))
+            NLA_PUT(msg,
+                    IFLA_BOND_AD_ACTOR_SYSTEM,
+                    sizeof(props->ad_actor_system),
+                    &props->ad_actor_system);
+        if (props->ad_select)
+            NLA_PUT_U8(msg, IFLA_BOND_AD_SELECT, props->ad_select);
+
+        NLA_PUT_U8(msg, IFLA_BOND_ALL_SLAVES_ACTIVE, props->all_ports_active);
+
+        if (props->fail_over_mac)
+            NLA_PUT_U8(msg, IFLA_BOND_FAIL_OVER_MAC, props->fail_over_mac);
+        if (props->lacp_rate)
+            NLA_PUT_U8(msg, IFLA_BOND_AD_LACP_RATE, props->lacp_rate);
+        if (props->num_grat_arp)
+            NLA_PUT_U8(msg, IFLA_BOND_NUM_PEER_NOTIF, props->num_grat_arp);
+
+        NLA_PUT_U8(msg, IFLA_BOND_MODE, props->mode);
+
+        if (props->primary_reselect)
+            NLA_PUT_U8(msg, IFLA_BOND_PRIMARY_RESELECT, props->primary_reselect);
+        if (props->xmit_hash_policy)
+            NLA_PUT_U8(msg, IFLA_BOND_XMIT_HASH_POLICY, props->xmit_hash_policy);
+        if (props->tlb_dynamic_lb_has)
+            NLA_PUT_U8(msg, IFLA_BOND_TLB_DYNAMIC_LB, !!props->tlb_dynamic_lb);
+
+        NLA_PUT_U8(msg, IFLA_BOND_USE_CARRIER, !!props->use_carrier);
+
+        break;
+    }
     case NM_LINK_TYPE_VLAN:
     {
         const NMPlatformLnkVlan *props = extra_data;
@@ -4655,8 +4966,8 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-_nl_msg_new_link_full(int         nlmsg_type,
-                      int         nlmsg_flags,
+_nl_msg_new_link_full(uint16_t    nlmsg_type,
+                      uint16_t    nlmsg_flags,
                       int         ifindex,
                       const char *ifname,
                       guint8      family,
@@ -4688,22 +4999,22 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-_nl_msg_new_link(int nlmsg_type, int nlmsg_flags, int ifindex, const char *ifname)
+_nl_msg_new_link(uint16_t nlmsg_type, uint16_t nlmsg_flags, int ifindex, const char *ifname)
 {
     return _nl_msg_new_link_full(nlmsg_type, nlmsg_flags, ifindex, ifname, AF_UNSPEC, 0, 0);
 }
 
 /* Copied and modified from libnl3's build_addr_msg(). */
 static struct nl_msg *
-_nl_msg_new_address(int           nlmsg_type,
-                    int           nlmsg_flags,
+_nl_msg_new_address(uint16_t      nlmsg_type,
+                    uint16_t      nlmsg_flags,
                     int           family,
                     int           ifindex,
                     gconstpointer address,
                     guint8        plen,
                     gconstpointer peer_address,
                     guint32       flags,
-                    int           scope,
+                    guint8        scope,
                     guint32       lifetime,
                     guint32       preferred,
                     in_addr_t     ip4_broadcast_address,
@@ -4715,6 +5026,7 @@ _nl_msg_new_address(int           nlmsg_type,
                      .ifa_index     = ifindex,
                      .ifa_prefixlen = plen,
                      .ifa_flags     = flags,
+                     .ifa_scope     = scope,
     };
     gsize addr_len;
 
@@ -4723,15 +5035,6 @@ _nl_msg_new_address(int           nlmsg_type,
 
     msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
 
-    if (scope == -1) {
-        /* Allow having scope unset, and detect the scope (including IPv4 compatibility hack). */
-        if (family == AF_INET && address && *((char *) address) == 127)
-            scope = RT_SCOPE_HOST;
-        else
-            scope = RT_SCOPE_UNIVERSE;
-    }
-    am.ifa_scope = scope,
-
     addr_len = family == AF_INET ? sizeof(in_addr_t) : sizeof(struct in6_addr);
 
     if (nlmsg_append_struct(msg, &am) < 0)
@@ -4783,7 +5086,8 @@ ip_route_get_lock_flag(const NMPlatformIPRoute *route)
            | (((guint32) route->lock_cwnd) << RTAX_CWND)
            | (((guint32) route->lock_initcwnd) << RTAX_INITCWND)
            | (((guint32) route->lock_initrwnd) << RTAX_INITRWND)
-           | (((guint32) route->lock_mtu) << RTAX_MTU);
+           | (((guint32) route->lock_mtu) << RTAX_MTU)
+           | (((guint32) route->lock_mss) << RTAX_ADVMSS);
 }
 
 static gboolean
@@ -4809,7 +5113,7 @@ ip_route_ignored_protocol(const NMPlatformIPRoute *route)
 
 /* Copied and modified from libnl3's build_route_msg() and rtnl_route_build_msg(). */
 static struct nl_msg *
-_nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj)
+_nl_msg_new_route(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPObject *obj)
 {
     nm_auto_nlmsg struct nl_msg *msg     = NULL;
     const NMPClass              *klass   = NMP_OBJECT_GET_CLASS(obj);
@@ -4836,7 +5140,7 @@ _nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj)
         NM_IN_SET(NMP_OBJECT_GET_TYPE(obj), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
     nm_assert(NM_IN_SET(nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
 
-    msg = nlmsg_alloc_simple(nlmsg_type, (int) nlmsgflags);
+    msg = nlmsg_alloc_simple(nlmsg_type, nlmsg_flags);
 
     if (nlmsg_append_struct(msg, &rtmsg) < 0)
         goto nla_put_failure;
@@ -4871,7 +5175,8 @@ _nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj)
     }
 
     if (obj->ip_route.mss || obj->ip_route.window || obj->ip_route.cwnd || obj->ip_route.initcwnd
-        || obj->ip_route.initrwnd || obj->ip_route.mtu || lock) {
+        || obj->ip_route.initrwnd || obj->ip_route.mtu || obj->ip_route.quickack
+        || obj->ip_route.rto_min || lock) {
         struct nlattr *metrics;
 
         metrics = nla_nest_start(msg, RTA_METRICS);
@@ -4890,6 +5195,10 @@ _nl_msg_new_route(int nlmsg_type, guint16 nlmsgflags, const NMPObject *obj)
             NLA_PUT_U32(msg, RTAX_INITRWND, obj->ip_route.initrwnd);
         if (obj->ip_route.mtu)
             NLA_PUT_U32(msg, RTAX_MTU, obj->ip_route.mtu);
+        if (obj->ip_route.rto_min)
+            NLA_PUT_U32(msg, RTAX_RTO_MIN, obj->ip_route.rto_min);
+        if (obj->ip_route.quickack)
+            NLA_PUT_U32(msg, RTAX_QUICKACK, obj->ip_route.quickack);
         if (lock)
             NLA_PUT_U32(msg, RTAX_LOCK, lock);
 
@@ -4915,7 +5224,9 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-_nl_msg_new_routing_rule(int nlmsg_type, int nlmsg_flags, const NMPlatformRoutingRule *routing_rule)
+_nl_msg_new_routing_rule(uint16_t                     nlmsg_type,
+                         uint16_t                     nlmsg_flags,
+                         const NMPlatformRoutingRule *routing_rule)
 {
     nm_auto_nlmsg struct nl_msg *msg = NULL;
     const guint8 addr_size           = nm_utils_addr_family_to_size(routing_rule->addr_family);
@@ -5028,7 +5339,7 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-_nl_msg_new_qdisc(int nlmsg_type, int nlmsg_flags, const NMPlatformQdisc *qdisc)
+_nl_msg_new_qdisc(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPlatformQdisc *qdisc)
 {
     nm_auto_nlmsg struct nl_msg *msg = NULL;
     struct nlattr               *tc_options;
@@ -5115,7 +5426,7 @@ nla_put_failure:
 }
 
 static struct nl_msg *
-_nl_msg_new_tfilter(int nlmsg_type, int nlmsg_flags, const NMPlatformTfilter *tfilter)
+_nl_msg_new_tfilter(uint16_t nlmsg_type, uint16_t nlmsg_flags, const NMPlatformTfilter *tfilter)
 {
     nm_auto_nlmsg struct nl_msg *msg = NULL;
     struct nlattr               *tc_options;
@@ -5154,7 +5465,7 @@ _nl_msg_new_tfilter(int nlmsg_type, int nlmsg_flags, const NMPlatformTfilter *tf
         if (nm_streq(action->kind, NM_PLATFORM_ACTION_KIND_SIMPLE)) {
             const NMPlatformActionSimple *simple = &action->simple;
             struct tc_defact              sel    = {
-                0,
+                                0,
             };
 
             if (!(act_options = nla_nest_start(msg, TCA_ACT_OPTIONS)))
@@ -5168,7 +5479,7 @@ _nl_msg_new_tfilter(int nlmsg_type, int nlmsg_flags, const NMPlatformTfilter *tf
         } else if (nm_streq(action->kind, NM_PLATFORM_ACTION_KIND_MIRRED)) {
             const NMPlatformActionMirred *mirred = &action->mirred;
             struct tc_mirred              sel    = {
-                0,
+                                0,
             };
 
             if (!(act_options = nla_nest_start(msg, TCA_ACT_OPTIONS)))
@@ -5204,14 +5515,6 @@ nla_put_failure:
 
 /*****************************************************************************/
 
-static struct nl_sock *
-_genl_sock(NMLinuxPlatform *platform)
-{
-    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-
-    return priv->genl;
-}
-
 #define ASSERT_SYSCTL_ARGS(pathid, dirfd, path)                                                 \
     G_STMT_START                                                                                \
     {                                                                                           \
@@ -5781,7 +6084,10 @@ sysctl_get(NMPlatform *platform, const char *pathid, int dirfd, const char *path
 static void
 process_events(NMPlatform *platform)
 {
-    delayed_action_handle_all(platform, TRUE);
+    delayed_action_schedule(platform,
+                            DELAYED_ACTION_TYPE_READ_RTNL | DELAYED_ACTION_TYPE_READ_GENL,
+                            NULL);
+    delayed_action_handle_all(platform);
 }
 
 /*****************************************************************************/
@@ -5789,27 +6095,34 @@ process_events(NMPlatform *platform)
 static const RefreshAllInfo *
 refresh_all_type_get_info(RefreshAllType refresh_all_type)
 {
+#define R(_protocol, _refresh_all_type, _obj_type, _addr_family) \
+    [_refresh_all_type] = {                                      \
+        .protocol             = _protocol,                       \
+        .obj_type             = _obj_type,                       \
+        .addr_family_for_dump = _addr_family,                    \
+    }
+#define R_ROUTE(...)   R(NMP_NETLINK_ROUTE, __VA_ARGS__)
+#define R_GENERIC(...) R(NMP_NETLINK_GENERIC, __VA_ARGS__)
     static const RefreshAllInfo infos[] = {
-#define R(_refresh_all_type, _obj_type, _addr_family) \
-    [_refresh_all_type] = {                           \
-        .obj_type    = _obj_type,                     \
-        .addr_family = _addr_family,                  \
-    }
-        R(REFRESH_ALL_TYPE_LINKS, NMP_OBJECT_TYPE_LINK, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_IP4_ADDRESSES, NMP_OBJECT_TYPE_IP4_ADDRESS, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_IP6_ADDRESSES, NMP_OBJECT_TYPE_IP6_ADDRESS, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_IP4_ROUTES, NMP_OBJECT_TYPE_IP4_ROUTE, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_IP6_ROUTES, NMP_OBJECT_TYPE_IP6_ROUTE, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_ROUTING_RULES_IP4, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET),
-        R(REFRESH_ALL_TYPE_ROUTING_RULES_IP6, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET6),
-        R(REFRESH_ALL_TYPE_QDISCS, NMP_OBJECT_TYPE_QDISC, AF_UNSPEC),
-        R(REFRESH_ALL_TYPE_TFILTERS, NMP_OBJECT_TYPE_TFILTER, AF_UNSPEC),
-#undef R
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_LINKS, NMP_OBJECT_TYPE_LINK, AF_UNSPEC),
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_IP4_ADDRESSES, NMP_OBJECT_TYPE_IP4_ADDRESS, AF_UNSPEC),
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_IP6_ADDRESSES, NMP_OBJECT_TYPE_IP6_ADDRESS, AF_UNSPEC),
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_IP4_ROUTES, NMP_OBJECT_TYPE_IP4_ROUTE, AF_UNSPEC),
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_IP6_ROUTES, NMP_OBJECT_TYPE_IP6_ROUTE, AF_UNSPEC),
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP4, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET),
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP6, NMP_OBJECT_TYPE_ROUTING_RULE, AF_INET6),
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_QDISCS, NMP_OBJECT_TYPE_QDISC, AF_UNSPEC),
+        R_ROUTE(REFRESH_ALL_TYPE_RTNL_TFILTERS, NMP_OBJECT_TYPE_TFILTER, AF_UNSPEC),
+        R_GENERIC(REFRESH_ALL_TYPE_GENL_FAMILIES, NMP_OBJECT_TYPE_UNKNOWN, AF_UNSPEC),
     };
+#undef R_GENERIC
+#undef R_ROUTE
+#undef R
 
     nm_assert(_NM_INT_NOT_NEGATIVE(refresh_all_type));
     nm_assert(refresh_all_type < G_N_ELEMENTS(infos));
-    nm_assert(nmp_class_from_type(infos[refresh_all_type].obj_type));
+    nm_assert(refresh_all_type == REFRESH_ALL_TYPE_GENL_FAMILIES
+              || nmp_class_from_type(infos[refresh_all_type].obj_type));
 
     return &infos[refresh_all_type];
 }
@@ -5819,19 +6132,24 @@ static NM_UTILS_LOOKUP_DEFINE(
     DelayedActionType,
     RefreshAllType,
     NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(0),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS, REFRESH_ALL_TYPE_LINKS),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES,
-                         REFRESH_ALL_TYPE_IP4_ADDRESSES),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES,
-                         REFRESH_ALL_TYPE_IP6_ADDRESSES),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES, REFRESH_ALL_TYPE_IP4_ROUTES),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, REFRESH_ALL_TYPE_IP6_ROUTES),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4,
-                         REFRESH_ALL_TYPE_ROUTING_RULES_IP4),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6,
-                         REFRESH_ALL_TYPE_ROUTING_RULES_IP6),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS, REFRESH_ALL_TYPE_QDISCS),
-    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, REFRESH_ALL_TYPE_TFILTERS),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_LINKS, REFRESH_ALL_TYPE_RTNL_LINKS),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ADDRESSES,
+                         REFRESH_ALL_TYPE_RTNL_IP4_ADDRESSES),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ADDRESSES,
+                         REFRESH_ALL_TYPE_RTNL_IP6_ADDRESSES),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ROUTES,
+                         REFRESH_ALL_TYPE_RTNL_IP4_ROUTES),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ROUTES,
+                         REFRESH_ALL_TYPE_RTNL_IP6_ROUTES),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_IP4,
+                         REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP4),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_IP6,
+                         REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP6),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_QDISCS, REFRESH_ALL_TYPE_RTNL_QDISCS),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_TFILTERS,
+                         REFRESH_ALL_TYPE_RTNL_TFILTERS),
+    NM_UTILS_LOOKUP_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_GENL_FAMILIES,
+                         REFRESH_ALL_TYPE_GENL_FAMILIES),
     NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), );
 
 static DelayedActionType
@@ -5853,25 +6171,25 @@ refresh_all_type_from_needle_object(const NMPObject *obj_needle)
 {
     switch (NMP_OBJECT_GET_TYPE(obj_needle)) {
     case NMP_OBJECT_TYPE_LINK:
-        return REFRESH_ALL_TYPE_LINKS;
+        return REFRESH_ALL_TYPE_RTNL_LINKS;
     case NMP_OBJECT_TYPE_IP4_ADDRESS:
-        return REFRESH_ALL_TYPE_IP4_ADDRESSES;
+        return REFRESH_ALL_TYPE_RTNL_IP4_ADDRESSES;
     case NMP_OBJECT_TYPE_IP6_ADDRESS:
-        return REFRESH_ALL_TYPE_IP6_ADDRESSES;
+        return REFRESH_ALL_TYPE_RTNL_IP6_ADDRESSES;
     case NMP_OBJECT_TYPE_IP4_ROUTE:
-        return REFRESH_ALL_TYPE_IP4_ROUTES;
+        return REFRESH_ALL_TYPE_RTNL_IP4_ROUTES;
     case NMP_OBJECT_TYPE_IP6_ROUTE:
-        return REFRESH_ALL_TYPE_IP6_ROUTES;
+        return REFRESH_ALL_TYPE_RTNL_IP6_ROUTES;
     case NMP_OBJECT_TYPE_QDISC:
-        return REFRESH_ALL_TYPE_QDISCS;
+        return REFRESH_ALL_TYPE_RTNL_QDISCS;
     case NMP_OBJECT_TYPE_TFILTER:
-        return REFRESH_ALL_TYPE_TFILTERS;
+        return REFRESH_ALL_TYPE_RTNL_TFILTERS;
     case NMP_OBJECT_TYPE_ROUTING_RULE:
         switch (NMP_OBJECT_CAST_ROUTING_RULE(obj_needle)->addr_family) {
         case AF_INET:
-            return REFRESH_ALL_TYPE_ROUTING_RULES_IP4;
+            return REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP4;
         case AF_INET6:
-            return REFRESH_ALL_TYPE_ROUTING_RULES_IP6;
+            return REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP6;
         }
         nm_assert_not_reached();
         return 0;
@@ -5895,11 +6213,11 @@ refresh_all_type_init_lookup(RefreshAllType refresh_all_type, NMPLookup *lookup)
     if (NM_IN_SET(refresh_all_info->obj_type, NMP_OBJECT_TYPE_ROUTING_RULE)) {
         return nmp_lookup_init_object_by_addr_family(lookup,
                                                      refresh_all_info->obj_type,
-                                                     refresh_all_info->addr_family);
+                                                     refresh_all_info->addr_family_for_dump);
     }
 
     /* not yet implemented. */
-    nm_assert(refresh_all_info->addr_family == AF_UNSPEC);
+    nm_assert(refresh_all_info->addr_family_for_dump == AF_UNSPEC);
 
     return nmp_lookup_init_obj_type(lookup, refresh_all_info->obj_type);
 }
@@ -5915,28 +6233,42 @@ static NM_UTILS_LOOKUP_STR_DEFINE(
     delayed_action_to_string,
     DelayedActionType,
     NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS, "refresh-all-links"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES,
-                             "refresh-all-ip4-addresses"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES,
-                             "refresh-all-ip6-addresses"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES, "refresh-all-ip4-routes"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES, "refresh-all-ip6-routes"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP4,
-                             "refresh-all-routing-rules-ip4"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_IP6,
-                             "refresh-all-routing-rules-ip6"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS, "refresh-all-qdiscs"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS, "refresh-all-tfilters"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_LINKS, "refresh-all-rtnl-links"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ADDRESSES,
+                             "refresh-all-rtnl-ip4-addresses"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ADDRESSES,
+                             "refresh-all-rtnl-ip6-addresses"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ROUTES,
+                             "refresh-all-rtnl-ip4-routes"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ROUTES,
+                             "refresh-all-rtnl-ip6-routes"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_IP4,
+                             "refresh-all-rtnl-routing-rules-ip4"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_IP6,
+                             "refresh-all-rtnl-routing-rules-ip6"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_QDISCS,
+                             "refresh-all-rtnl-qdiscs"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_TFILTERS,
+                             "refresh-all-rtnl-tfilters"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_ALL_GENL_FAMILIES,
+                             "refresh-all-genl-families"),
     NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_REFRESH_LINK, "refresh-link"),
     NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_MASTER_CONNECTED, "master-connected"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_READ_NETLINK, "read-netlink"),
-    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, "wait-for-nl-response"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_READ_RTNL, "read-rtnl"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_READ_GENL, "read-genl"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_RTNL, "wait-for-response-rtnl"),
+    NM_UTILS_LOOKUP_STR_ITEM(DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_GENL, "wait-for-response-genl"),
     NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_NONE),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL),
-    NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL),
+    NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_RTNL_ALL),
+    NM_UTILS_LOOKUP_ITEM_IGNORE(DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_ALL),
     NM_UTILS_LOOKUP_ITEM_IGNORE(__DELAYED_ACTION_TYPE_MAX), );
 
+#define delayed_action_get_list_wait_for_resonse(priv, netlink_protocol, idx)                   \
+    (&g_array_index((priv)->delayed_action.list_wait_for_response_x[nmp_netlink_protocol_check( \
+                        (netlink_protocol))],                                                   \
+                    DelayedActionWaitForNlResponseData,                                         \
+                    (idx)))
+
 static const char *
 delayed_action_to_string_full(DelayedActionType action_type,
                               gpointer          user_data,
@@ -5947,6 +6279,7 @@ delayed_action_to_string_full(DelayedActionType action_type,
     const DelayedActionWaitForNlResponseData *data;
 
     nm_strbuf_append_str(&buf, &buf_size, delayed_action_to_string(action_type));
+
     switch (action_type) {
     case DELAYED_ACTION_TYPE_MASTER_CONNECTED:
         nm_strbuf_append(&buf, &buf_size, " (master-ifindex %d)", GPOINTER_TO_INT(user_data));
@@ -5954,7 +6287,8 @@ delayed_action_to_string_full(DelayedActionType action_type,
     case DELAYED_ACTION_TYPE_REFRESH_LINK:
         nm_strbuf_append(&buf, &buf_size, " (ifindex %d)", GPOINTER_TO_INT(user_data));
         break;
-    case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE:
+    case DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_RTNL:
+    case DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_GENL:
         data = user_data;
 
         if (data) {
@@ -6005,8 +6339,8 @@ delayed_action_refresh_all_in_progress(NMPlatform *platform, DelayedActionType a
     RefreshAllType          refresh_all_type;
 
     nm_assert(nm_utils_is_power_of_two(action_type));
-    nm_assert(NM_FLAGS_ANY(action_type, DELAYED_ACTION_TYPE_REFRESH_ALL));
-    nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
+    nm_assert(NM_FLAGS_ANY(action_type, DELAYED_ACTION_TYPE_REFRESH_RTNL_ALL));
+    nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_RTNL_ALL));
 
     if (NM_FLAGS_ANY(priv->delayed_action.flags, action_type))
         return TRUE;
@@ -6016,25 +6350,26 @@ delayed_action_refresh_all_in_progress(NMPlatform *platform, DelayedActionType a
 }
 
 static void
-delayed_action_wait_for_nl_response_complete(NMPlatform             *platform,
-                                             guint                   idx,
-                                             WaitForNlResponseResult seq_result)
+delayed_action_wait_for_response_complete(NMPlatform             *platform,
+                                          NMPNetlinkProtocol      netlink_protocol,
+                                          guint                   idx,
+                                          WaitForNlResponseResult seq_result)
 {
     NMLinuxPlatformPrivate             *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
     DelayedActionWaitForNlResponseData *data;
+    const DelayedActionType             ACTION_TYPE =
+        nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response;
 
-    nm_assert(NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
-    nm_assert(idx < priv->delayed_action.list_wait_for_nl_response->len);
+    nm_assert(NM_FLAGS_ANY(priv->delayed_action.flags, ACTION_TYPE));
+    nm_assert(idx < priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len);
     nm_assert(seq_result);
 
-    data = &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                          DelayedActionWaitForNlResponseData,
-                          idx);
+    data = delayed_action_get_list_wait_for_resonse(priv, netlink_protocol, idx);
 
-    _LOGt_delayed_action(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, data, "complete");
+    _LOGt_delayed_action(ACTION_TYPE, data, "complete");
 
-    if (priv->delayed_action.list_wait_for_nl_response->len <= 1)
-        priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE;
+    if (priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len <= 1)
+        priv->delayed_action.flags &= ~ACTION_TYPE;
     if (data->out_seq_result)
         *data->out_seq_result = seq_result;
     switch (data->response_type) {
@@ -6055,15 +6390,16 @@ delayed_action_wait_for_nl_response_complete(NMPlatform             *platform,
         break;
     }
 
-    g_array_remove_index_fast(priv->delayed_action.list_wait_for_nl_response, idx);
+    g_array_remove_index_fast(priv->delayed_action.list_wait_for_response_x[netlink_protocol], idx);
 }
 
 static void
-delayed_action_wait_for_nl_response_complete_check(NMPlatform             *platform,
-                                                   WaitForNlResponseResult force_result,
-                                                   guint32                *out_next_seq_number,
-                                                   gint64                 *out_next_timeout_abs_ns,
-                                                   gint64                 *p_now_nsec)
+delayed_action_wait_for_response_complete_check(NMPlatform             *platform,
+                                                NMPNetlinkProtocol      netlink_protocol,
+                                                WaitForNlResponseResult force_result,
+                                                guint32                *out_next_seq_number,
+                                                gint64                 *out_next_timeout_abs_ns,
+                                                gint64                 *p_now_nsec)
 {
     NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
     guint                   i;
@@ -6071,24 +6407,25 @@ delayed_action_wait_for_nl_response_complete_check(NMPlatform             *platf
     gint64                  next_timeout_abs_ns = 0;
     gint64                  now_nsec            = 0;
 
-    for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len;) {
+    for (i = 0; i < priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len;) {
         const DelayedActionWaitForNlResponseData *data =
-            &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                           DelayedActionWaitForNlResponseData,
-                           i);
+            delayed_action_get_list_wait_for_resonse(priv, netlink_protocol, i);
 
         if (data->seq_result)
-            delayed_action_wait_for_nl_response_complete(platform, i, data->seq_result);
+            delayed_action_wait_for_response_complete(platform,
+                                                      netlink_protocol,
+                                                      i,
+                                                      data->seq_result);
         else if (p_now_nsec
                  && ((now_nsec ?: (now_nsec = nm_utils_get_monotonic_timestamp_nsec()))
                      >= data->timeout_abs_nsec)) {
             /* the caller can optionally check for timeout by providing a p_now_nsec argument. */
-            delayed_action_wait_for_nl_response_complete(
-                platform,
-                i,
-                WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
+            delayed_action_wait_for_response_complete(platform,
+                                                      netlink_protocol,
+                                                      i,
+                                                      WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
         } else if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
-            delayed_action_wait_for_nl_response_complete(platform, i, force_result);
+            delayed_action_wait_for_response_complete(platform, netlink_protocol, i, force_result);
         else {
             if (next_seq_number == 0 || next_timeout_abs_ns > data->timeout_abs_nsec) {
                 next_seq_number     = data->seq_number;
@@ -6099,9 +6436,10 @@ delayed_action_wait_for_nl_response_complete_check(NMPlatform             *platf
     }
 
     if (force_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN) {
-        nm_assert(
-            !NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
-        nm_assert(priv->delayed_action.list_wait_for_nl_response->len == 0);
+        nm_assert(!NM_FLAGS_ANY(
+            priv->delayed_action.flags,
+            nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response));
+        nm_assert(priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len == 0);
     }
 
     NM_SET_OUT(out_next_seq_number, next_seq_number);
@@ -6111,9 +6449,15 @@ delayed_action_wait_for_nl_response_complete_check(NMPlatform             *platf
 
 static void
 delayed_action_wait_for_nl_response_complete_all(NMPlatform             *platform,
+                                                 NMPNetlinkProtocol      netlink_protocol,
                                                  WaitForNlResponseResult fallback_result)
 {
-    delayed_action_wait_for_nl_response_complete_check(platform, fallback_result, NULL, NULL, NULL);
+    delayed_action_wait_for_response_complete_check(platform,
+                                                    nmp_netlink_protocol_check(netlink_protocol),
+                                                    fallback_result,
+                                                    NULL,
+                                                    NULL,
+                                                    NULL);
 }
 
 /*****************************************************************************/
@@ -6148,15 +6492,15 @@ delayed_action_handle_REFRESH_ALL(NMPlatform *platform, DelayedActionType flags)
 }
 
 static void
-delayed_action_handle_READ_NETLINK(NMPlatform *platform)
+delayed_action_handle_READ_NETLINK(NMPlatform *platform, NMPNetlinkProtocol netlink_protocol)
 {
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, netlink_protocol, FALSE);
 }
 
 static void
-delayed_action_handle_WAIT_FOR_NL_RESPONSE(NMPlatform *platform)
+delayed_action_handle_WAIT_FOR_RESPONSE(NMPlatform *platform, NMPNetlinkProtocol netlink_protocol)
 {
-    event_handler_read_netlink(platform, TRUE);
+    event_handler_read_netlink(platform, netlink_protocol, TRUE);
 }
 
 static gboolean
@@ -6164,6 +6508,8 @@ delayed_action_handle_one(NMPlatform *platform)
 {
     NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
     gpointer                user_data;
+    NMPNetlinkProtocol      netlink_protocol;
+    DelayedActionType       iflags;
 
     if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE)
         return FALSE;
@@ -6190,28 +6536,46 @@ delayed_action_handle_one(NMPlatform *platform)
     }
     nm_assert(priv->delayed_action.list_master_connected->len == 0);
 
-    /* Next we prefer read-netlink, because the buffer size is limited and we want to process events
+    /* Next we prefer read-genl/read-rtnl, because the buffer size is limited and we want to process events
      * from netlink early. */
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_READ_NETLINK)) {
-        _LOGt_delayed_action(DELAYED_ACTION_TYPE_READ_NETLINK, NULL, "handle");
-        priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_READ_NETLINK;
-        delayed_action_handle_READ_NETLINK(platform);
-        return TRUE;
+    for (netlink_protocol = _NMP_NETLINK_FIRST; netlink_protocol < _NMP_NETLINK_NUM;
+         netlink_protocol++) {
+        const DelayedActionType ACTION_TYPE =
+            nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_read;
+
+        if (NM_FLAGS_ANY(priv->delayed_action.flags, ACTION_TYPE)) {
+            _LOGt_delayed_action(ACTION_TYPE, NULL, "handle");
+            priv->delayed_action.flags &= ~ACTION_TYPE;
+            delayed_action_handle_READ_NETLINK(platform, netlink_protocol);
+            return TRUE;
+        }
     }
 
-    if (NM_FLAGS_ANY(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_ALL)) {
-        DelayedActionType flags, iflags;
+    if (NM_FLAGS_ANY(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_GENL_ALL)) {
+        const DelayedActionType FLAGS =
+            priv->delayed_action.flags & DELAYED_ACTION_TYPE_REFRESH_GENL_ALL;
 
-        flags = priv->delayed_action.flags & DELAYED_ACTION_TYPE_REFRESH_ALL;
+        if (_LOGt_ENABLED()) {
+            FOR_EACH_DELAYED_ACTION (iflags, FLAGS)
+                _LOGt_delayed_action(iflags, NULL, "handle");
+        }
 
-        priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_ALL;
+        priv->delayed_action.flags &= ~FLAGS;
+        delayed_action_handle_REFRESH_ALL(platform, FLAGS);
+        return TRUE;
+    }
+
+    if (NM_FLAGS_ANY(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_RTNL_ALL)) {
+        const DelayedActionType FLAGS =
+            (priv->delayed_action.flags & DELAYED_ACTION_TYPE_REFRESH_RTNL_ALL);
 
         if (_LOGt_ENABLED()) {
-            FOR_EACH_DELAYED_ACTION (iflags, flags)
+            FOR_EACH_DELAYED_ACTION (iflags, FLAGS)
                 _LOGt_delayed_action(iflags, NULL, "handle");
         }
 
-        delayed_action_handle_REFRESH_ALL(platform, flags);
+        priv->delayed_action.flags &= ~FLAGS;
+        delayed_action_handle_REFRESH_ALL(platform, FLAGS);
         return TRUE;
     }
 
@@ -6235,18 +6599,24 @@ delayed_action_handle_one(NMPlatform *platform)
         return TRUE;
     }
 
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-        nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0);
-        _LOGt_delayed_action(DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, NULL, "handle");
-        delayed_action_handle_WAIT_FOR_NL_RESPONSE(platform);
-        return TRUE;
+    for (netlink_protocol = _NMP_NETLINK_FIRST; netlink_protocol < _NMP_NETLINK_NUM;
+         netlink_protocol++) {
+        const DelayedActionType ACTION_TYPE =
+            nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response;
+
+        if (NM_FLAGS_ANY(priv->delayed_action.flags, ACTION_TYPE)) {
+            nm_assert(priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len > 0);
+            _LOGt_delayed_action(ACTION_TYPE, NULL, "handle");
+            delayed_action_handle_WAIT_FOR_RESPONSE(platform, netlink_protocol);
+            return TRUE;
+        }
     }
 
     return FALSE;
 }
 
 static gboolean
-delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink)
+delayed_action_handle_all(NMPlatform *platform)
 {
     NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
     gboolean                any  = FALSE;
@@ -6254,8 +6624,6 @@ delayed_action_handle_all(NMPlatform *platform, gboolean read_netlink)
     g_return_val_if_fail(priv->delayed_action.is_handling == 0, FALSE);
 
     priv->delayed_action.is_handling++;
-    if (read_netlink)
-        delayed_action_schedule(platform, DELAYED_ACTION_TYPE_READ_NETLINK, NULL);
     while (delayed_action_handle_one(platform))
         any = TRUE;
     priv->delayed_action.is_handling--;
@@ -6290,14 +6658,21 @@ delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpo
             < 0)
             g_ptr_array_add(priv->delayed_action.list_master_connected, user_data);
         break;
-    case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE:
-        g_array_append_vals(priv->delayed_action.list_wait_for_nl_response, user_data, 1);
+    case DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_RTNL:
+        g_array_append_vals(priv->delayed_action.list_wait_for_response_rtnl, user_data, 1);
+        break;
+    case DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_GENL:
+        g_array_append_vals(priv->delayed_action.list_wait_for_response_genl, user_data, 1);
         break;
     default:
+        /* For other action types, we support setting multiple flags at once. They
+         * also don't have any user-data. */
         nm_assert(!user_data);
-        nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_REFRESH_LINK));
-        nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED));
-        nm_assert(!NM_FLAGS_HAS(action_type, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+        nm_assert(!NM_FLAGS_ANY(action_type,
+                                DELAYED_ACTION_TYPE_REFRESH_LINK
+                                    | DELAYED_ACTION_TYPE_MASTER_CONNECTED
+                                    | DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_RTNL
+                                    | DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_GENL));
         break;
     }
 
@@ -6310,12 +6685,37 @@ delayed_action_schedule(NMPlatform *platform, DelayedActionType action_type, gpo
 }
 
 static void
-delayed_action_schedule_WAIT_FOR_NL_RESPONSE(NMPlatform                        *platform,
-                                             guint32                            seq_number,
-                                             WaitForNlResponseResult           *out_seq_result,
-                                             char                             **out_errmsg,
-                                             DelayedActionWaitForNlResponseType response_type,
-                                             gpointer                           response_out_data)
+delayed_action_schedule_refresh_all(NMPlatform *platform, NMPNetlinkProtocol netlink_protocol)
+{
+    DelayedActionType action_type;
+
+    if (netlink_protocol == NMP_NETLINK_ROUTE) {
+        action_type = DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_LINKS
+                      | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ADDRESSES
+                      | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ADDRESSES
+                      | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ROUTES
+                      | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ROUTES
+                      | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_ALL;
+        if (nm_platform_get_cache_tc(platform)) {
+            action_type |= (DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_QDISCS
+                            | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_TFILTERS);
+        }
+    } else {
+        nm_assert(netlink_protocol == NMP_NETLINK_GENERIC);
+        action_type = DELAYED_ACTION_TYPE_REFRESH_ALL_GENL_FAMILIES;
+    }
+
+    delayed_action_schedule(platform, action_type, NULL);
+}
+
+static void
+delayed_action_schedule_WAIT_FOR_RESPONSE(NMPlatform                        *platform,
+                                          NMPNetlinkProtocol                 netlink_protocol,
+                                          guint32                            seq_number,
+                                          WaitForNlResponseResult           *out_seq_result,
+                                          char                             **out_errmsg,
+                                          DelayedActionWaitForNlResponseType response_type,
+                                          gpointer                           response_out_data)
 {
     DelayedActionWaitForNlResponseData data = {
         .seq_number = seq_number,
@@ -6327,7 +6727,10 @@ delayed_action_schedule_WAIT_FOR_NL_RESPONSE(NMPlatform                        *
         .response.out_data = response_out_data,
     };
 
-    delayed_action_schedule(platform, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, &data);
+    delayed_action_schedule(
+        platform,
+        nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response,
+        &data);
 }
 
 /*****************************************************************************/
@@ -6379,6 +6782,9 @@ cache_prune_all(NMPlatform *platform)
 
         if (priv->pruning[refresh_all_type] == 0)
             continue;
+
+        nm_assert(refresh_all_type != REFRESH_ALL_TYPE_GENL_FAMILIES);
+
         priv->pruning[refresh_all_type] -= 1;
         if (priv->pruning[refresh_all_type] > 0)
             continue;
@@ -6462,15 +6868,18 @@ cache_on_change(NMPlatform      *platform,
                 ifindex = obj_new->link.ifindex;
 
             if (ifindex > 0) {
-                delayed_action_schedule(platform,
-                                        DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
-                                        NULL);
+                delayed_action_schedule(
+                    platform,
+                    DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ADDRESSES
+                        | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ADDRESSES
+                        | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ROUTES
+                        | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ROUTES
+                        | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_ALL
+                        | (nm_platform_get_cache_tc(platform)
+                               ? (DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_QDISCS
+                                  | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_TFILTERS)
+                               : DELAYED_ACTION_TYPE_NONE),
+                    NULL);
             }
         }
         {
@@ -6518,8 +6927,8 @@ cache_on_change(NMPlatform      *platform,
                  * think kernel does send RTM_DELROUTE events for IPv6 routes, so
                  * we might not need to refresh IPv6 routes. */
                 delayed_action_schedule(platform,
-                                        DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+                                        DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ROUTES
+                                            | DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ROUTES,
                                         NULL);
             }
         }
@@ -6627,8 +7036,8 @@ cache_on_change(NMPlatform      *platform,
         if (cache_op == NMP_CACHE_OPS_REMOVED) {
             delayed_action_schedule(platform,
                                     (klass->obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS)
-                                        ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-                                        : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+                                        ? DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP4_ROUTES
+                                        : DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_IP6_ROUTES,
                                     NULL);
         }
     } break;
@@ -6640,12 +7049,14 @@ cache_on_change(NMPlatform      *platform,
 /*****************************************************************************/
 
 static guint32
-_nlh_seq_next_get(NMLinuxPlatformPrivate *priv)
+_nlh_seq_next_get(NMLinuxPlatformPrivate *priv, NMPNetlinkProtocol netlink_protocol)
 {
+    guint32 *p = &priv->proto_data_x[netlink_protocol].nlh_seq_next;
+
     /* generate a new sequence number, but never return zero.
      * Wrapping numbers are not a problem, because we don't rely
      * on strictly increasing sequence numbers. */
-    return (++priv->nlh_seq_next) ?: (++priv->nlh_seq_next);
+    return (++(*p)) ?: (++(*p));
 }
 
 /**
@@ -6672,7 +7083,7 @@ _nl_send_nlmsghdr(NMPlatform                        *platform,
 
     nm_assert(nlhdr);
 
-    seq              = _nlh_seq_next_get(priv);
+    seq              = _nlh_seq_next_get(priv, NMP_NETLINK_ROUTE);
     nlhdr->nlmsg_seq = seq;
 
     {
@@ -6689,12 +7100,12 @@ _nl_send_nlmsghdr(NMPlatform                        *platform,
         int try_count;
 
         if (!nlhdr->nlmsg_pid)
-            nlhdr->nlmsg_pid = nl_socket_get_local_port(priv->nlh);
+            nlhdr->nlmsg_pid = nl_socket_get_local_port(priv->sk_rtnl);
         nlhdr->nlmsg_flags |= (NLM_F_REQUEST | NLM_F_ACK);
 
         try_count = 0;
 again:
-        errsv = sendmsg(nl_socket_get_fd(priv->nlh), &msg, 0);
+        errsv = sendmsg(nl_socket_get_fd(priv->sk_rtnl), &msg, 0);
         if (errsv < 0) {
             errsv = errno;
             if (errsv == EINTR && try_count++ < 100)
@@ -6706,32 +7117,24 @@ again:
         }
     }
 
-    delayed_action_schedule_WAIT_FOR_NL_RESPONSE(platform,
-                                                 seq,
-                                                 out_seq_result,
-                                                 out_errmsg,
-                                                 response_type,
-                                                 response_out_data);
+    delayed_action_schedule_WAIT_FOR_RESPONSE(platform,
+                                              NMP_NETLINK_ROUTE,
+                                              seq,
+                                              out_seq_result,
+                                              out_errmsg,
+                                              response_type,
+                                              response_out_data);
     return 0;
 }
 
-/**
- * _nl_send_nlmsg:
- * @platform:
- * @nlmsg:
- * @out_seq_result:
- * @response_type:
- * @response_out_data:
- *
- * Returns: 0 on success, or a negative libnl3 error code (beware, it's not an errno).
- */
 static int
-_nl_send_nlmsg(NMPlatform                        *platform,
-               struct nl_msg                     *nlmsg,
-               WaitForNlResponseResult           *out_seq_result,
-               char                             **out_errmsg,
-               DelayedActionWaitForNlResponseType response_type,
-               gpointer                           response_out_data)
+_netlink_send_nlmsg(NMPlatform                        *platform,
+                    NMPNetlinkProtocol                 netlink_protocol,
+                    struct nl_msg                     *nlmsg,
+                    WaitForNlResponseResult           *out_seq_result,
+                    char                             **out_errmsg,
+                    DelayedActionWaitForNlResponseType response_type,
+                    gpointer                           response_out_data)
 {
     NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
     struct nlmsghdr        *nlhdr;
@@ -6739,24 +7142,40 @@ _nl_send_nlmsg(NMPlatform                        *platform,
     int                     nle;
 
     nlhdr            = nlmsg_hdr(nlmsg);
-    seq              = _nlh_seq_next_get(priv);
+    seq              = _nlh_seq_next_get(priv, netlink_protocol);
     nlhdr->nlmsg_seq = seq;
 
-    nle = nl_send_auto(priv->nlh, nlmsg);
+    nle = nl_send_auto(priv->sk_x[netlink_protocol], nlmsg);
     if (nle < 0) {
         _LOGD("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nm_strerror(nle), nle);
         return nle;
     }
 
-    delayed_action_schedule_WAIT_FOR_NL_RESPONSE(platform,
-                                                 seq,
-                                                 out_seq_result,
-                                                 out_errmsg,
-                                                 response_type,
-                                                 response_out_data);
+    delayed_action_schedule_WAIT_FOR_RESPONSE(platform,
+                                              netlink_protocol,
+                                              seq,
+                                              out_seq_result,
+                                              out_errmsg,
+                                              response_type,
+                                              response_out_data);
     return 0;
 }
 
+static int
+_netlink_send_nlmsg_rtnl(NMPlatform              *platform,
+                         struct nl_msg           *nlmsg,
+                         WaitForNlResponseResult *out_seq_result,
+                         char                   **out_errmsg)
+{
+    return _netlink_send_nlmsg(platform,
+                               NMP_NETLINK_ROUTE,
+                               nlmsg,
+                               out_seq_result,
+                               out_errmsg,
+                               DELAYED_ACTION_RESPONSE_TYPE_VOID,
+                               NULL);
+}
+
 static void
 do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char *name)
 {
@@ -6776,16 +7195,16 @@ do_request_link_no_delayed_actions(NMPlatform *platform, int ifindex, const char
 
         entry = nmp_cache_lookup_entry_link(nm_platform_get_cache(platform), ifindex);
         if (entry) {
-            priv->pruning[REFRESH_ALL_TYPE_LINKS] += 1;
+            priv->pruning[REFRESH_ALL_TYPE_RTNL_LINKS] += 1;
             nm_dedup_multi_entry_set_dirty(entry, TRUE);
         }
     }
 
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
     nlmsg = _nl_msg_new_link(RTM_GETLINK, 0, ifindex, name);
     if (nlmsg) {
-        nle = _nl_send_nlmsg(platform, nlmsg, NULL, NULL, DELAYED_ACTION_RESPONSE_TYPE_VOID, NULL);
+        nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, NULL, NULL);
         if (nle < 0) {
             _LOGE("do-request-link: %d %s: failed sending netlink request \"%s\" (%d)",
                   ifindex,
@@ -6801,11 +7220,11 @@ static void
 do_request_link(NMPlatform *platform, int ifindex, const char *name)
 {
     do_request_link_no_delayed_actions(platform, ifindex, name);
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 }
 
 static struct nl_msg *
-_nl_msg_new_dump(NMPObjectType obj_type, int preferred_addr_family)
+_nl_msg_new_dump_rtnl(NMPObjectType obj_type, int preferred_addr_family)
 {
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
     const NMPClass              *klass;
@@ -6855,6 +7274,26 @@ _nl_msg_new_dump(NMPObjectType obj_type, int preferred_addr_family)
     return g_steal_pointer(&nlmsg);
 }
 
+static struct nl_msg *
+_nl_msg_new_dump_genl_families(void)
+{
+    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+
+    nlmsg = nlmsg_alloc_size(nlmsg_total_size(GENL_HDRLEN));
+
+    if (!genlmsg_put(nlmsg,
+                     NL_AUTO_PORT,
+                     NL_AUTO_SEQ,
+                     GENL_ID_CTRL,
+                     0,
+                     NLM_F_DUMP,
+                     CTRL_CMD_GETFAMILY,
+                     1))
+        g_return_val_if_reached(NULL);
+
+    return g_steal_pointer(&nlmsg);
+}
+
 static void
 do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action_type)
 {
@@ -6862,29 +7301,36 @@ do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action
     DelayedActionType       action_type_prune;
     DelayedActionType       iflags;
 
-    nm_assert(!NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
-    action_type &= DELAYED_ACTION_TYPE_REFRESH_ALL;
+    nm_assert((NM_FLAGS_ANY(action_type, DELAYED_ACTION_TYPE_REFRESH_RTNL_ALL)
+               && !NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_RTNL_ALL))
+              || (NM_FLAGS_ANY(action_type, DELAYED_ACTION_TYPE_REFRESH_GENL_ALL)
+                  && !NM_FLAGS_ANY(action_type, ~DELAYED_ACTION_TYPE_REFRESH_GENL_ALL)));
 
     action_type_prune = action_type;
 
-    /* calling nmp_cache_dirty_set_all_main() with a non-main lookup-index requires an extra
-     * cache lookup for every entry.
-     *
-     * Avoid that, by special casing routing-rules here. */
-    if (NM_FLAGS_ALL(action_type_prune, DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL)) {
+    if (NM_FLAGS_ALL(action_type_prune, DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_ALL)) {
         NMPLookup lookup;
 
-        priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP4] += 1;
-        priv->pruning[REFRESH_ALL_TYPE_ROUTING_RULES_IP6] += 1;
+        /* calling nmp_cache_dirty_set_all_main() with a non-main lookup-index requires an extra
+         * cache lookup for every entry.
+         *
+         * Avoid that, by special casing routing-rules here. */
+        priv->pruning[REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP4] += 1;
+        priv->pruning[REFRESH_ALL_TYPE_RTNL_ROUTING_RULES_IP6] += 1;
         nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
         nmp_cache_dirty_set_all_main(nm_platform_get_cache(platform), &lookup);
-        action_type_prune &= ~DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL;
+        action_type_prune &= ~DELAYED_ACTION_TYPE_REFRESH_ALL_RTNL_ROUTING_RULES_ALL;
     }
-
     FOR_EACH_DELAYED_ACTION (iflags, action_type_prune) {
         RefreshAllType refresh_all_type = delayed_action_type_to_refresh_all_type(iflags);
         NMPLookup      lookup;
 
+        if (refresh_all_type == REFRESH_ALL_TYPE_GENL_FAMILIES) {
+            /* genl families are not tracked in the NMPObject cache, because there is
+             * only a static number of objects we care about (NMPGenlFamilyType). */
+            continue;
+        }
+
         priv->pruning[refresh_all_type] += 1;
         refresh_all_type_init_lookup(refresh_all_type, &lookup);
         nmp_cache_dirty_set_all_main(nm_platform_get_cache(platform), &lookup);
@@ -6905,7 +7351,7 @@ do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action
         priv->delayed_action.flags &= ~iflags;
         _LOGt_delayed_action(iflags, NULL, "handle (do-request-all)");
 
-        if (refresh_all_type == REFRESH_ALL_TYPE_LINKS) {
+        if (refresh_all_type == REFRESH_ALL_TYPE_RTNL_LINKS) {
             nm_assert(
                 (priv->delayed_action.list_refresh_link->len > 0)
                 == NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK));
@@ -6918,23 +7364,30 @@ do_request_all_no_delayed_actions(NMPlatform *platform, DelayedActionType action
             }
         }
 
-        event_handler_read_netlink(platform, FALSE);
+        event_handler_read_netlink(platform, refresh_all_info->protocol, FALSE);
+
+        if (refresh_all_info->protocol == NMP_NETLINK_ROUTE) {
+            nlmsg = _nl_msg_new_dump_rtnl(refresh_all_info->obj_type,
+                                          refresh_all_info->addr_family_for_dump);
+        } else {
+            nm_assert(refresh_all_type == REFRESH_ALL_TYPE_GENL_FAMILIES);
+            nlmsg = _nl_msg_new_dump_genl_families();
+        }
 
-        nlmsg = _nl_msg_new_dump(refresh_all_info->obj_type, refresh_all_info->addr_family);
         if (!nlmsg)
             goto next_after_fail;
 
-        if (_nl_send_nlmsg(platform,
-                           nlmsg,
-                           NULL,
-                           NULL,
-                           DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS,
-                           out_refresh_all_in_progress)
+        if (_netlink_send_nlmsg(platform,
+                                refresh_all_info->protocol,
+                                nlmsg,
+                                NULL,
+                                NULL,
+                                DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS,
+                                out_refresh_all_in_progress)
             < 0)
             goto next_after_fail;
 
         continue;
-
 next_after_fail:
         nm_assert(*out_refresh_all_in_progress > 0);
         *out_refresh_all_in_progress -= 1;
@@ -6946,102 +7399,109 @@ do_request_one_type_by_needle_object(NMPlatform *platform, const NMPObject *obj_
 {
     do_request_all_no_delayed_actions(platform,
                                       delayed_action_refresh_from_needle_object(obj_needle));
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 }
 
 static void
-event_seq_check_refresh_all(NMPlatform *platform, guint32 seq_number)
+event_seq_check_refresh_all(NMPlatform        *platform,
+                            NMPNetlinkProtocol netlink_protocol,
+                            guint32            seq_number)
 {
-    NMLinuxPlatformPrivate             *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    DelayedActionWaitForNlResponseData *data;
-    guint                               i;
+    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+    guint                   i;
 
-    if (NM_IN_SET(seq_number, 0, priv->nlh_seq_last_seen))
+    if (NM_IN_SET(seq_number, 0, priv->proto_data_x[netlink_protocol].nlh_seq_last_seen))
         return;
 
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-        nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0);
+    if (!NM_FLAGS_ANY(
+            priv->delayed_action.flags,
+            nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response))
+        goto out;
 
-        for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
-            data = &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                                  DelayedActionWaitForNlResponseData,
-                                  i);
+    nm_assert(priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len > 0);
 
-            if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS
-                && data->response.out_refresh_all_in_progress
-                && data->seq_number == priv->nlh_seq_last_seen) {
-                *data->response.out_refresh_all_in_progress -= 1;
-                data->response.out_refresh_all_in_progress = NULL;
-                break;
-            }
+    for (i = 0; i < priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len; i++) {
+        DelayedActionWaitForNlResponseData *data =
+            delayed_action_get_list_wait_for_resonse(priv, netlink_protocol, i);
+
+        if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_REFRESH_ALL_IN_PROGRESS
+            && data->response.out_refresh_all_in_progress
+            && data->seq_number == priv->proto_data_x[netlink_protocol].nlh_seq_last_seen) {
+            *data->response.out_refresh_all_in_progress -= 1;
+            data->response.out_refresh_all_in_progress = NULL;
+            break;
         }
     }
 
-    priv->nlh_seq_last_seen = seq_number;
+out:
+    priv->proto_data_x[netlink_protocol].nlh_seq_last_seen = seq_number;
 }
 
 static void
 event_seq_check(NMPlatform             *platform,
+                NMPNetlinkProtocol      netlink_protocol,
                 guint32                 seq_number,
                 WaitForNlResponseResult seq_result,
                 const char             *msg)
 {
-    NMLinuxPlatformPrivate             *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    DelayedActionWaitForNlResponseData *data;
-    guint                               i;
+    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+    guint                   i;
 
     if (seq_number == 0)
         return;
 
-    if (NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
-        nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0);
+    if (!NM_FLAGS_ANY(
+            priv->delayed_action.flags,
+            nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response))
+        goto out;
+
+    nm_assert(priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len > 0);
 
-        for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
-            data = &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                                  DelayedActionWaitForNlResponseData,
-                                  i);
+    for (i = 0; i < priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len; i++) {
+        DelayedActionWaitForNlResponseData *data =
+            delayed_action_get_list_wait_for_resonse(priv, netlink_protocol, i);
 
-            if (data->seq_number == seq_number) {
-                /* We potentially receive many parts partial responses for the same sequence number.
+        if (data->seq_number == seq_number) {
+            /* We potentially receive many parts partial responses for the same sequence number.
                  * Thus, we only remember the result, and collect it later. */
-                if (data->seq_result < 0) {
-                    /* we already saw an error for this sequence number.
+            if (data->seq_result < 0) {
+                /* we already saw an error for this sequence number.
                      * Preserve it. */
-                } else if (seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
-                           || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
-                    data->seq_result = seq_result;
-                if (data->out_errmsg && !*data->out_errmsg)
-                    *data->out_errmsg = g_strdup(msg);
-                return;
-            }
+            } else if (seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
+                       || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
+                data->seq_result = seq_result;
+            if (data->out_errmsg && !*data->out_errmsg)
+                *data->out_errmsg = g_strdup(msg);
+            return;
         }
     }
 
+out:
+
 #if NM_MORE_LOGGING
-    if (seq_number != priv->nlh_seq_last_handled)
+    if (seq_number != priv->proto_data_x[netlink_protocol].nlh_seq_last_handled)
         _LOGt("netlink: recvmsg: unwaited sequence number %u", seq_number);
-    priv->nlh_seq_last_handled = seq_number;
+    priv->proto_data_x[netlink_protocol].nlh_seq_last_handled = seq_number;
+#else
+    (void) 0;
 #endif
 }
 
 static void
-event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events)
+_rtnl_handle_msg(NMPlatform *platform, const struct nl_msg_lite *msg)
 {
     char                      sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE];
     NMLinuxPlatformPrivate   *priv;
     nm_auto_nmpobj NMPObject *obj = NULL;
     NMPCacheOpsType           cache_op;
-    struct nlmsghdr          *msghdr;
+    const struct nlmsghdr    *msghdr;
     char                      buf_nlmsghdr[400];
     gboolean                  is_del  = FALSE;
     gboolean                  is_dump = FALSE;
     NMPCache                 *cache   = nm_platform_get_cache(platform);
     ParseNlmsgIter            parse_nlmsg_iter;
 
-    if (!handle_events)
-        return;
-
-    msghdr = nlmsg_hdr(msg);
+    msghdr = msg->nm_nlh;
 
     if (NM_IN_SET(msghdr->nlmsg_type,
                   RTM_DELLINK,
@@ -7062,7 +7522,7 @@ event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events
     obj = nmp_object_new_from_nl(platform, cache, msg, is_del, &parse_nlmsg_iter);
     if (!obj) {
         _LOGT("event-notification: %s: ignore",
-              nl_nlmsghdr_to_str(msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)));
+              nl_nlmsghdr_to_str(NETLINK_ROUTE, 0, msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)));
         return;
     }
 
@@ -7080,7 +7540,7 @@ event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events
     }
 
     _LOGT("event-notification: %s%s: %s",
-          nl_nlmsghdr_to_str(msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)),
+          nl_nlmsghdr_to_str(NETLINK_ROUTE, 0, msghdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)),
           is_dump ? ", in-dump" : "",
           nmp_object_to_string(obj,
                                is_del ? NMP_OBJECT_TO_STRING_ID : NMP_OBJECT_TO_STRING_PUBLIC,
@@ -7119,20 +7579,18 @@ event_valid_msg(NMPlatform *platform, struct nl_msg *msg, gboolean handle_events
                 nm_assert(is_ipv6 || !nmp_object_is_alive(obj));
                 priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
                 if (NM_FLAGS_HAS(priv->delayed_action.flags,
-                                 DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+                                 DELAYED_ACTION_TYPE_WAIT_FOR_RESPONSE_RTNL)) {
                     guint i;
 
-                    nm_assert(priv->delayed_action.list_wait_for_nl_response->len > 0);
-                    for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; i++) {
+                    nm_assert(priv->delayed_action.list_wait_for_response_rtnl->len > 0);
+                    for (i = 0; i < priv->delayed_action.list_wait_for_response_rtnl->len; i++) {
                         DelayedActionWaitForNlResponseData *data =
-                            &g_array_index(priv->delayed_action.list_wait_for_nl_response,
-                                           DelayedActionWaitForNlResponseData,
-                                           i);
+                            delayed_action_get_list_wait_for_resonse(priv, NMP_NETLINK_ROUTE, i);
 
                         if (data->response_type == DELAYED_ACTION_RESPONSE_TYPE_ROUTE_GET
                             && data->response.out_route_get) {
                             nm_assert(!*data->response.out_route_get);
-                            if (data->seq_number == nlmsg_hdr(msg)->nlmsg_seq) {
+                            if (data->seq_number == msg->nm_nlh->nlmsg_seq) {
                                 *data->response.out_route_get = nmp_object_clone(obj, FALSE);
                                 data->response.out_route_get  = NULL;
                                 break;
@@ -7269,14 +7727,9 @@ do_add_link_with_lookup(NMPlatform            *platform,
     char                    s_buf[256];
     NMPCache               *cache = nm_platform_get_cache(platform);
 
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _nl_send_nlmsg(platform,
-                         nlmsg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
+    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &errmsg);
     if (nle < 0) {
         _LOGE("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
               name,
@@ -7287,7 +7740,7 @@ do_add_link_with_lookup(NMPlatform            *platform,
         return nle;
     }
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     nm_assert(seq_result);
 
@@ -7323,14 +7776,9 @@ do_add_addrroute(NMPlatform      *platform,
                         NMP_OBJECT_TYPE_IP4_ROUTE,
                         NMP_OBJECT_TYPE_IP6_ROUTE));
 
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _nl_send_nlmsg(platform,
-                         nlmsg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
+    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &errmsg);
     if (nle < 0) {
         _LOGE("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
               NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
@@ -7340,7 +7788,7 @@ do_add_addrroute(NMPlatform      *platform,
         return -NME_PL_NETLINK;
     }
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     nm_assert(seq_result);
 
@@ -7379,14 +7827,9 @@ do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *n
     gboolean                success;
     const char             *log_detail = "";
 
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _nl_send_nlmsg(platform,
-                         nlmsg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
+    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &errmsg);
     if (nle < 0) {
         _LOGE("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
               NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
@@ -7396,7 +7839,7 @@ do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *n
         return FALSE;
     }
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     nm_assert(seq_result);
 
@@ -7470,12 +7913,7 @@ do_change_link(NMPlatform           *platform,
     }
 
 retry:
-    nle = _nl_send_nlmsg(platform,
-                         nlmsg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
+    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &errmsg);
     if (nle < 0) {
         log_level = LOGL_ERR;
         log_detail_free =
@@ -7488,7 +7926,7 @@ retry:
      * and we sometimes lack events. Nuke it from the orbit... */
     delayed_action_schedule(platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER(ifindex));
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     nm_assert(seq_result);
 
@@ -7760,7 +8198,7 @@ link_set_address(NMPlatform *platform, int ifindex, gconstpointer address, size_
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
     const ChangeLinkData         d     = {
                     .set_address =
-            {
+                        {
                             .address = address,
                             .length  = length,
             },
@@ -8596,11 +9034,11 @@ link_can_assume(NMPlatform *platform, int ifindex)
     if (link->link.master > 0)
         return TRUE;
 
-    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex);
+    nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex);
     if (nmp_cache_lookup(cache, &lookup))
         return TRUE;
 
-    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex);
+    nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex);
     nmp_cache_iter_for_each (&iter, nmp_cache_lookup(cache, &lookup), &o) {
         nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP6_ADDRESS);
         if (!IN6_IS_ADDR_LINKLOCAL(&o->ip6_address.address))
@@ -8750,8 +9188,7 @@ ip4_address_add(NMPlatform *platform,
                                 plen,
                                 &peer_addr,
                                 flags,
-                                nm_utils_ip4_address_is_link_local(addr) ? RT_SCOPE_LINK
-                                                                         : RT_SCOPE_UNIVERSE,
+                                nm_platform_ip4_address_get_scope(addr),
                                 lifetime,
                                 preferred,
                                 broadcast_address,
@@ -8897,6 +9334,9 @@ object_delete(NMPlatform *platform, const NMPObject *obj)
     case NMP_OBJECT_TYPE_TFILTER:
         nlmsg = _nl_msg_new_tfilter(RTM_DELTFILTER, 0, NMP_OBJECT_CAST_TFILTER(obj));
         break;
+    case NMP_OBJECT_TYPE_MPTCP_ADDR:
+        return (nm_platform_mptcp_addr_update(platform, FALSE, NMP_OBJECT_CAST_MPTCP_ADDR(obj))
+                >= 0);
     default:
         break;
     }
@@ -8967,7 +9407,7 @@ ip_route_get(NMPlatform   *platform,
             return -NME_UNSPEC;
         }
 
-        delayed_action_handle_all(platform, FALSE);
+        delayed_action_handle_all(platform);
 
         /* Retry, if we failed due to a cache resync. That can happen when the netlink
          * socket fills up and we lost the response. */
@@ -9003,20 +9443,15 @@ routing_rule_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformRoutin
 
     msg = _nl_msg_new_routing_rule(RTM_NEWRULE, flags, routing_rule);
 
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _nl_send_nlmsg(platform,
-                         msg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
+    nle = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &errmsg);
     if (nle < 0) {
         _LOGE("do-add-rule: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
         return -NME_PL_NETLINK;
     }
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     nm_assert(seq_result);
 
@@ -9047,20 +9482,15 @@ qdisc_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformQdisc *qdisc)
 
     msg = _nl_msg_new_qdisc(RTM_NEWQDISC, flags, qdisc);
 
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _nl_send_nlmsg(platform,
-                         msg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
+    nle = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &errmsg);
     if (nle < 0) {
         _LOGE("do-add-qdisc: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
         return -NME_PL_NETLINK;
     }
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     nm_assert(seq_result);
 
@@ -9076,7 +9506,11 @@ qdisc_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformQdisc *qdisc)
 }
 
 static int
-tc_delete(NMPlatform *platform, int nlmsgtype, int ifindex, guint32 parent, gboolean log_error)
+tc_delete(NMPlatform *platform,
+          uint16_t    nlmsg_type,
+          int         ifindex,
+          guint32     parent,
+          gboolean    log_error)
 {
     WaitForNlResponseResult      seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
     gs_free char                *errmsg     = NULL;
@@ -9089,7 +9523,7 @@ tc_delete(NMPlatform *platform, int nlmsgtype, int ifindex, guint32 parent, gboo
                   .tcm_parent  = parent,
     };
 
-    switch (nlmsgtype) {
+    switch (nlmsg_type) {
     case RTM_DELQDISC:
         log_tag = "do-delete-qdisc";
         break;
@@ -9101,19 +9535,14 @@ tc_delete(NMPlatform *platform, int nlmsgtype, int ifindex, guint32 parent, gboo
         log_tag = "do-delete-tc";
     }
 
-    msg = nlmsg_alloc_simple(nlmsgtype, NMP_NLM_FLAG_F_ECHO);
+    msg = nlmsg_alloc_simple(nlmsg_type, NMP_NLM_FLAG_F_ECHO);
 
     if (nlmsg_append_struct(msg, &tcm) < 0)
         goto nla_put_failure;
 
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _nl_send_nlmsg(platform,
-                         msg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
+    nle = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &errmsg);
     if (nle < 0) {
         _NMLOG(log_error ? LOGL_ERR : LOGL_DEBUG,
                "%s: failed sending netlink request \"%s\" (%d)",
@@ -9123,7 +9552,7 @@ tc_delete(NMPlatform *platform, int nlmsgtype, int ifindex, guint32 parent, gboo
         return -NME_PL_NETLINK;
     }
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     nm_assert(seq_result);
 
@@ -9163,20 +9592,15 @@ tfilter_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformTfilter *tf
 
     msg = _nl_msg_new_tfilter(RTM_NEWTFILTER, flags, tfilter);
 
-    event_handler_read_netlink(platform, FALSE);
+    event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _nl_send_nlmsg(platform,
-                         msg,
-                         &seq_result,
-                         &errmsg,
-                         DELAYED_ACTION_RESPONSE_TYPE_VOID,
-                         NULL);
+    nle = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &errmsg);
     if (nle < 0) {
         _LOGE("do-add-tfilter: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
         return -NME_PL_NETLINK;
     }
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     nm_assert(seq_result);
 
@@ -9199,93 +9623,210 @@ tfilter_delete(NMPlatform *platform, int ifindex, guint32 parent, gboolean log_e
 /*****************************************************************************/
 
 static gboolean
-event_handler(int fd, GIOCondition io_condition, gpointer user_data)
+_genl_family_id_update(NMPlatform *platform, NMPGenlFamilyType family_type, guint16 family_id)
 {
-    delayed_action_handle_all(NM_PLATFORM(user_data), TRUE);
+    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+
+    if (priv->genl_family_data[family_type].family_id == family_id)
+        return FALSE;
+
+    if (family_id != 0) {
+        _LOGD("genl:ctrl: new family-id for %s: 0x%x",
+              nmp_genl_family_infos[family_type].name,
+              family_id);
+    } else
+        _LOGD("genl:ctrl: del family-id for %s", nmp_genl_family_infos[family_type].name);
+    priv->genl_family_data[family_type].family_id = family_id;
     return TRUE;
 }
 
+static void
+_genl_handle_msg_ctrl(NMPlatform *platform, const struct nlmsghdr *hdr)
+{
+    const struct genlmsghdr *ghdr = nlmsg_data(hdr);
+
+    switch (ghdr->cmd) {
+    case CTRL_CMD_NEWFAMILY:
+    case CTRL_CMD_DELFAMILY:
+    {
+        struct nlattr    *tb[G_N_ELEMENTS(genl_ctrl_policy)];
+        NMPGenlFamilyType family_type;
+        const char       *name;
+        guint16           family_id = 0;
+
+        if (genlmsg_parse_arr(hdr, 0, tb, genl_ctrl_policy) < 0)
+            return;
+
+        name        = nla_get_string(tb[CTRL_ATTR_FAMILY_NAME]);
+        family_type = nmp_genl_family_type_from_name(name);
+
+        if (family_type == _NMP_GENL_FAMILY_TYPE_NONE)
+            return;
+
+        if (ghdr->cmd == CTRL_CMD_NEWFAMILY)
+            family_id = nla_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
+
+        _genl_family_id_update(platform, family_type, family_id);
+    }
+    }
+}
+
+static void
+_genl_handle_msg(NMPlatform *platform, guint32 pktinfo_group, const struct nl_msg_lite *msg)
+{
+    const struct nlmsghdr *hdr = msg->nm_nlh;
+
+    if (!genlmsg_valid_hdr(hdr, 0))
+        return;
+
+    if (hdr->nlmsg_type == GENL_ID_CTRL)
+        _genl_handle_msg_ctrl(platform, hdr);
+}
+
 /*****************************************************************************/
 
-/* copied from libnl3's recvmsgs() */
 static int
-event_handler_recvmsgs(NMPlatform *platform, gboolean handle_events)
+_netlink_recv(NMPlatform         *platform,
+              struct nl_sock     *sk,
+              struct sockaddr_nl *nla,
+              struct ucred       *out_creds,
+              gboolean           *out_creds_has,
+              guint32            *out_pktinfo_group,
+              gboolean           *out_pktinfo_has)
 {
     NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
-    struct nl_sock         *sk   = priv->nlh;
+    unsigned char          *buf  = NULL;
     int                     n;
-    int                     err         = 0;
-    gboolean                multipart   = 0;
-    gboolean                interrupted = FALSE;
-    struct nlmsghdr        *hdr;
-    WaitForNlResponseResult seq_result;
-    struct sockaddr_nl      nla;
-    struct ucred            creds;
-    gboolean                creds_has;
-    unsigned char          *buf;
 
-continue_reading:
-    buf = NULL;
+    nm_assert(nla);
+    nm_assert(out_creds);
+    nm_assert(out_creds_has);
+
+    /* We use a pre-allocated receive buffer. We use it both for sk_rtnl
+     * and sk_genl. We can do that, because we are deep inside the netlink
+     * handling, and we never will need to use it for both sockets at the
+     * same time. */
 
     n = nl_recv(sk,
                 priv->netlink_recv_buf.buf,
                 priv->netlink_recv_buf.len,
-                &nla,
+                nla,
                 &buf,
-                &creds,
-                &creds_has);
+                out_creds,
+                out_creds_has,
+                out_pktinfo_group,
+                out_pktinfo_has);
 
     nm_assert((n <= 0 && !buf)
               || (n > 0 && n <= priv->netlink_recv_buf.len && buf == priv->netlink_recv_buf.buf));
 
-    if (n <= 0) {
-        if (n == -NME_NL_MSG_TRUNC) {
-            /* the message receive buffer was too small. We lost one message, which
-             * is unfortunate. Try to double the buffer size for the next time. */
-            priv->netlink_recv_buf.len *= 2;
-            priv->netlink_recv_buf.buf =
-                g_realloc(priv->netlink_recv_buf.buf, priv->netlink_recv_buf.len);
-            _LOGT("netlink: recvmsg: increase message buffer size for recvmsg() to %zu bytes",
-                  priv->netlink_recv_buf.len);
-            if (!handle_events)
-                goto continue_reading;
-        }
-
-        return n;
+    if (n == -NME_NL_MSG_TRUNC) {
+        /* the message receive buffer was too small. We lost one message, which
+         * is unfortunate. Try to double the buffer size for the next time. */
+        priv->netlink_recv_buf.len *= 2;
+        priv->netlink_recv_buf.buf =
+            g_realloc(priv->netlink_recv_buf.buf, priv->netlink_recv_buf.len);
+        _LOGT("netlink: recvmsg: increase message buffer size for recvmsg() to %zu bytes",
+              priv->netlink_recv_buf.len);
     }
 
-    hdr = (struct nlmsghdr *) buf;
-    while (nlmsg_ok(hdr, n)) {
-        nm_auto_nlmsg struct nl_msg *msg               = NULL;
-        gboolean                     abort_parsing     = FALSE;
-        gboolean                     process_valid_msg = FALSE;
-        guint32                      seq_number;
-        char                         buf_nlmsghdr[400];
-        const char                  *extack_msg = NULL;
+    return n;
+}
 
-        msg = nlmsg_alloc_convert(hdr);
+/*****************************************************************************/
 
-        nlmsg_set_proto(msg, NETLINK_ROUTE);
-        nlmsg_set_src(msg, &nla);
+static gboolean
+_nl_event_handler(NMPlatform *platform, DelayedActionType action_type)
+{
+    delayed_action_schedule(platform, action_type, NULL);
+    delayed_action_handle_all(platform);
+    return TRUE;
+}
 
-        if (!creds_has || creds.pid) {
-            if (!creds_has)
-                _LOGT("netlink: recvmsg: received message without credentials");
-            else
-                _LOGT("netlink: recvmsg: received non-kernel message (pid %d)", creds.pid);
-            err = 0;
-            goto stop;
-        }
+static gboolean
+_nl_event_handler_genl(int fd, GIOCondition io_condition, gpointer user_data)
+{
+    return _nl_event_handler(user_data, DELAYED_ACTION_TYPE_READ_GENL);
+}
+
+static gboolean
+_nl_event_handler_rtnl(int fd, GIOCondition io_condition, gpointer user_data)
+{
+    return _nl_event_handler(user_data, DELAYED_ACTION_TYPE_READ_RTNL);
+}
+
+/*****************************************************************************/
+
+static int
+_netlink_recv_handle(NMPlatform        *platform,
+                     NMPNetlinkProtocol netlink_protocol,
+                     gboolean           handle_events)
+{
+    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+    int                     n;
+    int                     retval      = 0;
+    gboolean                multipart   = 0;
+    gboolean                interrupted = FALSE;
+    struct nlmsghdr        *hdr;
+    struct sockaddr_nl      nla;
+    struct ucred            creds;
+    gboolean                creds_has;
+    guint32                 pktinfo_group = 0;
+    gboolean                pktinfo_has   = FALSE;
+    const char *const       log_prefix    = nmp_netlink_protocol_info(netlink_protocol)->name;
+
+continue_reading:
+
+    n = _netlink_recv(platform,
+                      priv->sk_x[netlink_protocol],
+                      &nla,
+                      &creds,
+                      &creds_has,
+                      &pktinfo_group,
+                      netlink_protocol == NMP_NETLINK_GENERIC ? &pktinfo_has : NULL);
+    if (n < 0) {
+        if (n == -NME_NL_MSG_TRUNC && !handle_events)
+            goto continue_reading;
+        return n;
+    }
+
+    if (!creds_has || creds.pid) {
+        if (!creds_has)
+            _LOGT("%s: recvmsg: received message without credentials", log_prefix);
+        else
+            _LOGT("%s: recvmsg: received non-kernel message (pid %d)", log_prefix, creds.pid);
+        goto stop;
+    }
 
-        _LOGt("netlink: recvmsg: new message %s",
-              nl_nlmsghdr_to_str(hdr, buf_nlmsghdr, sizeof(buf_nlmsghdr)));
+    hdr = (struct nlmsghdr *) priv->netlink_recv_buf.buf;
+    while (nlmsg_ok(hdr, n)) {
+        WaitForNlResponseResult  seq_result;
+        gboolean                 process_valid_msg = FALSE;
+        char                     buf_nlmsghdr[400];
+        const char              *extack_msg = NULL;
+        const struct nl_msg_lite msg        = {
+                   .nm_protocol = nmp_netlink_protocol_info(netlink_protocol)->netlink_protocol,
+                   .nm_src      = &nla,
+                   .nm_creds    = &creds,
+                   .nm_size     = NLMSG_ALIGN(hdr->nlmsg_len),
+                   .nm_nlh      = hdr,
+        };
+        const guint32 seq_number = msg.nm_nlh->nlmsg_seq;
+
+        nm_assert((((uintptr_t) (const void *) msg.nm_nlh) % NLMSG_ALIGNTO) == 0);
 
-        nlmsg_set_creds(msg, &creds);
+        _LOGt("%s: recvmsg: new message %s",
+              log_prefix,
+              nl_nlmsghdr_to_str(nmp_netlink_protocol_info(netlink_protocol)->netlink_protocol,
+                                 pktinfo_group,
+                                 msg.nm_nlh,
+                                 buf_nlmsghdr,
+                                 sizeof(buf_nlmsghdr)));
 
-        if (hdr->nlmsg_flags & NLM_F_MULTI)
+        if (msg.nm_nlh->nlmsg_flags & NLM_F_MULTI)
             multipart = TRUE;
 
-        if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
+        if (msg.nm_nlh->nlmsg_flags & NLM_F_DUMP_INTR) {
             /*
              * We have to continue reading to clear
              * all messages until a NLMSG_DONE is
@@ -9294,105 +9835,75 @@ continue_reading:
             interrupted = TRUE;
         }
 
-        /* Other side wishes to see an ack for this message */
-        if (hdr->nlmsg_flags & NLM_F_ACK) {
-            /* FIXME: implement */
+        if (msg.nm_nlh->nlmsg_flags & NLM_F_ACK) {
+            /* TODO: Other side wishes to see an ack for this message */
         }
 
         seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
 
-        if (hdr->nlmsg_type == NLMSG_DONE) {
+        if (msg.nm_nlh->nlmsg_type == NLMSG_DONE) {
             /* messages terminates a multipart message, this is
              * usually the end of a message and therefore we slip
              * out of the loop by default. the user may overrule
              * this action by skipping this packet. */
             multipart  = FALSE;
             seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
-        } else if (hdr->nlmsg_type == NLMSG_NOOP) {
+        } else if (msg.nm_nlh->nlmsg_type == NLMSG_NOOP) {
             /* Message to be ignored, the default action is to
              * skip this message if no callback is specified. The
              * user may overrule this action by returning
              * NL_PROCEED. */
-        } else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
+        } else if (msg.nm_nlh->nlmsg_type == NLMSG_OVERRUN) {
             /* Data got lost, report back to user. The default action is to
              * quit parsing. The user may overrule this action by returning
              * NL_SKIP or NL_PROCEED (dangerous) */
-            err           = -NME_NL_MSG_OVERFLOW;
-            abort_parsing = TRUE;
-        } else if (hdr->nlmsg_type == NLMSG_ERROR) {
-            /* Message carries a nlmsgerr */
-            struct nlmsgerr *e = nlmsg_data(hdr);
-
-            if (hdr->nlmsg_len < nlmsg_size(sizeof(*e))) {
-                /* Truncated error message, the default action
-                 * is to stop parsing. The user may overrule
-                 * this action by returning NL_SKIP or
-                 * NL_PROCEED (dangerous) */
-                err           = -NME_NL_MSG_TRUNC;
-                abort_parsing = TRUE;
-            } else if (e->error) {
-                int errsv = nm_errno_native(e->error);
-
-                if (NM_FLAGS_HAS(hdr->nlmsg_flags, NLM_F_ACK_TLVS)
-                    && hdr->nlmsg_len >= sizeof(*e) + e->msg.nlmsg_len) {
-                    static const struct nla_policy policy[] = {
-                        [NLMSGERR_ATTR_MSG]  = {.type = NLA_STRING},
-                        [NLMSGERR_ATTR_OFFS] = {.type = NLA_U32},
-                    };
-                    struct nlattr *tb[G_N_ELEMENTS(policy)];
-                    struct nlattr *tlvs;
-
-                    tlvs = (struct nlattr *) ((char *) e + sizeof(*e) + e->msg.nlmsg_len
-                                              - NLMSG_HDRLEN);
-                    if (nla_parse_arr(tb,
-                                      tlvs,
-                                      hdr->nlmsg_len - sizeof(*e) - e->msg.nlmsg_len,
-                                      policy)
-                        >= 0) {
-                        if (tb[NLMSGERR_ATTR_MSG])
-                            extack_msg = nla_get_string(tb[NLMSGERR_ATTR_MSG]);
-                    }
-                }
+            retval = -NME_NL_MSG_OVERFLOW;
+        } else if (msg.nm_nlh->nlmsg_type == NLMSG_ERROR) {
+            int errsv;
 
-                /* Error message reported back from kernel. */
-                _LOGD("netlink: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d",
-                      nm_strerror_native(errsv),
+            errsv = nlmsg_parse_error(msg.nm_nlh, &extack_msg);
+            if (errsv == 0)
+                seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+            else {
+                _LOGD("%s: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d",
+                      log_prefix,
+                      nm_strerror(errsv),
                       errsv,
                       NM_PRINT_FMT_QUOTED(extack_msg, " \"", extack_msg, "\"", ""),
-                      nlmsg_hdr(msg)->nlmsg_seq);
-                seq_result = -NM_ERRNO_NATIVE(errsv);
-            } else
-                seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+                      msg.nm_nlh->nlmsg_seq);
+                seq_result = errsv;
+            }
         } else
             process_valid_msg = TRUE;
 
-        seq_number = nlmsg_hdr(msg)->nlmsg_seq;
-
         /* check whether the seq number is different from before, and
          * whether the previous number (@nlh_seq_last_seen) is a pending
          * refresh-all request. In that case, the pending request is thereby
          * completed.
          *
-         * We must do that before processing the message with event_valid_msg(),
+         * We must do that before processing the message with _rtnl_handle_msg(),
          * because we must track the completion of the pending request before that. */
-        event_seq_check_refresh_all(platform, seq_number);
+        event_seq_check_refresh_all(platform, netlink_protocol, seq_number);
 
         if (process_valid_msg) {
-            /* Valid message (not checking for MULTIPART bit to
-             * get along with broken kernels. NL_SKIP has no
-             * effect on this.  */
-
-            event_valid_msg(platform, msg, handle_events);
-
+            if (handle_events) {
+                /* Valid message (not checking for MULTIPART bit to
+                 * get along with broken kernels. NL_SKIP has no
+                 * effect on this.  */
+                if (netlink_protocol == NMP_NETLINK_ROUTE) {
+                    _rtnl_handle_msg(platform, &msg);
+                } else {
+                    _genl_handle_msg(platform, pktinfo_group, &msg);
+                }
+            }
             seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
         }
 
-        event_seq_check(platform, seq_number, seq_result, extack_msg);
+        event_seq_check(platform, netlink_protocol, seq_number, seq_result, extack_msg);
 
-        if (abort_parsing)
+        if (retval != 0)
             goto stop;
 
-        err = 0;
         hdr = nlmsg_next(hdr, &n);
     }
 
@@ -9400,6 +9911,7 @@ continue_reading:
         /* Multipart message not yet complete, continue reading */
         goto continue_reading;
     }
+
 stop:
     if (!handle_events) {
         /* when we don't handle events, we want to drain all messages from the socket
@@ -9410,13 +9922,15 @@ stop:
 
     if (interrupted)
         return -NME_NL_DUMP_INTR;
-    return err;
+    return retval;
 }
 
 /*****************************************************************************/
 
 static gboolean
-event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks)
+event_handler_read_netlink(NMPlatform        *platform,
+                           NMPNetlinkProtocol netlink_protocol,
+                           gboolean           wait_for_acks)
 {
     nm_auto_pop_netns NMPNetns *netns = NULL;
     NMLinuxPlatformPrivate     *priv  = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
@@ -9430,8 +9944,11 @@ event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks)
         gint64  now_nsec;
     } next;
 
+    nmp_netlink_protocol_check(netlink_protocol);
+
     if (!nm_platform_netns_push(platform, &netns)) {
         delayed_action_wait_for_nl_response_complete_all(platform,
+                                                         netlink_protocol,
                                                          WAIT_FOR_NL_RESPONSE_RESULT_FAILED_SETNS);
         return FALSE;
     }
@@ -9440,20 +9957,24 @@ event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks)
         for (;;) {
             int nle;
 
-            nle = event_handler_recvmsgs(platform, TRUE);
+            nle = _netlink_recv_handle(platform, netlink_protocol, TRUE);
 
             if (nle < 0) {
                 switch (nle) {
                 case -EAGAIN:
                     goto after_read;
                 case -NME_NL_DUMP_INTR:
-                    _LOGD("netlink: read: uncritical failure to retrieve incoming events: %s (%d)",
+                    _LOGD("netlink[%s]: read: uncritical failure to retrieve incoming events: %s "
+                          "(%d)",
+                          nmp_netlink_protocol_info(netlink_protocol)->name,
                           nm_strerror(nle),
                           nle);
                     break;
                 case -NME_NL_MSG_TRUNC:
                 case -ENOBUFS:
-                    _LOGI("netlink: read: %s. Need to resynchronize platform cache", ({
+                    _LOGI("netlink[%s]: read: %s. Need to resynchronize platform cache",
+                          nmp_netlink_protocol_info(netlink_protocol)->name,
+                          ({
                               const char *_reason = "unknown";
                               switch (nle) {
                               case -NME_NL_MSG_TRUNC:
@@ -9465,24 +9986,16 @@ event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks)
                               }
                               _reason;
                           }));
-                    event_handler_recvmsgs(platform, FALSE);
+                    _netlink_recv_handle(platform, netlink_protocol, FALSE);
                     delayed_action_wait_for_nl_response_complete_all(
                         platform,
+                        netlink_protocol,
                         WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
-
-                    delayed_action_schedule(platform,
-                                            DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS
-                                                | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
-                                            NULL);
+                    delayed_action_schedule_refresh_all(platform, netlink_protocol);
                     break;
                 default:
-                    _LOGE("netlink: read: failed to retrieve incoming events: %s (%d)",
+                    _LOGE("netlink[%s]: read: failed to retrieve incoming events: %s (%d)",
+                          nmp_netlink_protocol_info(netlink_protocol)->name,
                           nm_strerror(nle),
                           nle);
                     break;
@@ -9493,17 +10006,22 @@ event_handler_read_netlink(NMPlatform *platform, gboolean wait_for_acks)
 
 after_read:
 
-        if (!NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
+        if (!NM_FLAGS_ANY(
+                priv->delayed_action.flags,
+                nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response))
             return any;
 
-        delayed_action_wait_for_nl_response_complete_check(platform,
-                                                           WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN,
-                                                           &next.seq_number,
-                                                           &next.timeout_abs_nsec,
-                                                           &next.now_nsec);
+        delayed_action_wait_for_response_complete_check(platform,
+                                                        netlink_protocol,
+                                                        WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN,
+                                                        &next.seq_number,
+                                                        &next.timeout_abs_nsec,
+                                                        &next.now_nsec);
 
         if (!wait_for_acks
-            || !NM_FLAGS_HAS(priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
+            || !NM_FLAGS_ANY(
+                priv->delayed_action.flags,
+                nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response))
             return any;
 
         nm_assert(next.seq_number);
@@ -9521,7 +10039,7 @@ after_read:
               timeout_msec);
 
         memset(&pfd, 0, sizeof(pfd));
-        pfd.fd     = nl_socket_get_fd(priv->nlh);
+        pfd.fd     = nl_socket_get_fd(priv->sk_rtnl);
         pfd.events = POLLIN;
         r          = poll(&pfd, 1, timeout_msec);
 
@@ -9539,6 +10057,7 @@ after_read:
                 _LOGE("netlink: read: poll failed with %s", nm_strerror_native(errsv));
                 delayed_action_wait_for_nl_response_complete_all(
                     platform,
+                    netlink_protocol,
                     WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL);
                 return any;
             }
@@ -9549,6 +10068,337 @@ after_read:
 
 /*****************************************************************************/
 
+static guint16
+genl_get_family_id(NMPlatform *platform, NMPGenlFamilyType family_type)
+{
+    NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+    int                     family_id;
+
+    nm_assert(_NM_INT_NOT_NEGATIVE(family_type));
+    nm_assert(family_type < G_N_ELEMENTS(priv->genl_family_data));
+
+    if (priv->genl_family_data[family_type].family_id != 0)
+        goto out;
+
+    /* Unknown family ID... usually we expect to start using the protocol.
+     * Let's try harder and fetch the ID synchronously.
+     *
+     * Note that we might also be called back during delayed_action_handle_all(),
+     * when we add a WireGuard link, the module gets autoloaded, and we didn't
+     * yet process the genl notification about the new family. Let's not call
+     * delayed_action_handle_all() again, because that might emit various
+     * signals. */
+
+    family_id = genl_ctrl_resolve(priv->sk_genl_sync, nmp_genl_family_infos[family_type].name);
+    if (family_id < 0)
+        family_id = 0;
+
+    /* We cache the family ID and update it via genl notifications.
+     * Here we bypass the order of that, and update the cached value
+     * directly. */
+    _genl_family_id_update(platform, family_type, family_id);
+
+out:
+    return priv->genl_family_data[family_type].family_id;
+}
+
+/*****************************************************************************/
+
+static const NMPObject *
+_mptcp_addrs_dump_parse_addr(struct nlattr *attr)
+{
+    static const struct nla_policy policy[] = {
+        [MPTCP_PM_ADDR_ATTR_FAMILY] = {.type = NLA_U16},
+        [MPTCP_PM_ADDR_ATTR_ID]     = {.type = NLA_U8},
+        [MPTCP_PM_ADDR_ATTR_ADDR4]  = {.minlen = sizeof(in_addr_t)},
+        [MPTCP_PM_ADDR_ATTR_ADDR6]  = {.minlen = sizeof(struct in6_addr)},
+        [MPTCP_PM_ADDR_ATTR_PORT]   = {.type = NLA_U16},
+        [MPTCP_PM_ADDR_ATTR_FLAGS]  = {.type = NLA_U32},
+        [MPTCP_PM_ADDR_ATTR_IF_IDX] = {.type = NLA_S32},
+    };
+    struct nlattr            *tb[G_N_ELEMENTS(policy)];
+    nm_auto_nmpobj NMPObject *obj = NULL;
+    NMPlatformMptcpAddr      *mptcp_addr;
+    int                       addr_family;
+    int                       addr_attr;
+
+    if (nla_parse_nested_arr(tb, attr, policy) < 0)
+        return NULL;
+
+    if (!tb[MPTCP_PM_ADDR_ATTR_ID])
+        return NULL;
+
+    obj        = nmp_object_new(NMP_OBJECT_TYPE_MPTCP_ADDR, NULL);
+    mptcp_addr = &obj->mptcp_addr;
+
+    mptcp_addr->id = nla_get_u8(tb[MPTCP_PM_ADDR_ATTR_ID]);
+
+    if (!tb[MPTCP_PM_ADDR_ATTR_FAMILY]) {
+        /* If we don't have the family. Only create a stub object containing
+         * the ID. */
+        goto out;
+    }
+
+    addr_family = nla_get_u16(tb[MPTCP_PM_ADDR_ATTR_FAMILY]);
+
+    if (addr_family == AF_INET)
+        addr_attr = MPTCP_PM_ADDR_ATTR_ADDR4;
+    else if (addr_family == AF_INET6)
+        addr_attr = MPTCP_PM_ADDR_ATTR_ADDR6;
+    else
+        goto out;
+
+    if (!tb[addr_attr])
+        goto out;
+
+    mptcp_addr->addr_family = addr_family;
+    memcpy(&mptcp_addr->addr, nla_data(tb[addr_attr]), nm_utils_addr_family_to_size(addr_family));
+
+    if (tb[MPTCP_PM_ADDR_ATTR_PORT])
+        mptcp_addr->port = nla_get_u16(tb[MPTCP_PM_ADDR_ATTR_PORT]);
+
+    if (tb[MPTCP_PM_ADDR_ATTR_IF_IDX])
+        mptcp_addr->ifindex = nla_get_s32(tb[MPTCP_PM_ADDR_ATTR_IF_IDX]);
+
+    if (tb[MPTCP_PM_ADDR_ATTR_FLAGS])
+        mptcp_addr->flags = nla_get_u32(tb[MPTCP_PM_ADDR_ATTR_FLAGS]);
+
+out:
+
+    return g_steal_pointer(&obj);
+}
+
+typedef struct {
+    GPtrArray *addrs;
+} FetchMptcpAddrParseData;
+
+static int
+_mptcp_addrs_dump_parse_cb(const struct nl_msg *msg, void *arg)
+{
+    static const struct nla_policy policy[] = {
+        [MPTCP_PM_ATTR_ADDR] = {.type = NLA_NESTED},
+    };
+    struct nlattr           *tb[G_N_ELEMENTS(policy)];
+    FetchMptcpAddrParseData *parse_data = arg;
+
+    if (genlmsg_parse_arr(nlmsg_hdr(msg), 0, tb, policy) < 0)
+        return NL_SKIP;
+
+    if (tb[MPTCP_PM_ATTR_ADDR]) {
+        const NMPObject *obj;
+
+        obj = _mptcp_addrs_dump_parse_addr(tb[MPTCP_PM_ATTR_ADDR]);
+        if (obj)
+            g_ptr_array_add(parse_data->addrs, (gpointer) obj);
+    }
+
+    return NL_OK;
+}
+
+#define EXTACK_MSG_BUFSIZE 200
+
+static int
+_mptcp_addr_update_err_cb(const struct sockaddr_nl *nla, const struct nlmsgerr *nlerr, void *arg)
+{
+    char       *out_extack_msg = arg;
+    const char *extack_msg;
+    int         errsv;
+
+    errsv = nlmsg_parse_error(nlmsg_undata(nlerr), &extack_msg);
+    if (extack_msg)
+        g_strlcpy(out_extack_msg, extack_msg, EXTACK_MSG_BUFSIZE);
+    return errsv;
+}
+
+static int
+mptcp_addr_update(NMPlatform *platform, NMOptionBool add, const NMPlatformMptcpAddr *addr)
+{
+    NMLinuxPlatformPrivate      *priv  = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+    struct nlattr               *nla_nest;
+    char                         sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    guint16                      genl_family_id;
+    guint8                       cmd_num;
+    const char                  *cmd_str;
+    int                          nle;
+    char                         extack_msg[EXTACK_MSG_BUFSIZE];
+    const struct nl_cb           cb = {
+                  .err_cb  = _mptcp_addr_update_err_cb,
+                  .err_arg = extack_msg,
+    };
+
+    extack_msg[0] = '\0';
+
+    if (add == NM_OPTION_BOOL_DEFAULT) {
+        cmd_num = MPTCP_PM_CMD_SET_FLAGS;
+        cmd_str = "update";
+    } else if (add) {
+        cmd_num = MPTCP_PM_CMD_ADD_ADDR;
+        cmd_str = "add";
+    } else {
+        cmd_num = MPTCP_PM_CMD_DEL_ADDR;
+        cmd_str = "delete";
+    }
+
+    nm_assert_addr_family_or_unspec(addr->addr_family);
+    nm_assert(cmd_num != MPTCP_PM_CMD_ADD_ADDR
+              || (addr->port == 0 && addr->addr_family != AF_UNSPEC));
+    nm_assert(cmd_num != MPTCP_PM_CMD_SET_FLAGS || (addr->id != 0 && addr->port == 0));
+    nm_assert(cmd_num != MPTCP_PM_CMD_DEL_ADDR || addr->id != 0);
+
+    genl_family_id = nm_platform_genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_MPTCP_PM);
+    if (genl_family_id == 0) {
+        _LOGT("mptcp: %s address %s fails because %s generic netlink family is unknown",
+              cmd_str,
+              nm_platform_mptcp_addr_to_string(addr, sbuf, sizeof(sbuf)),
+              nmp_genl_family_infos[NMP_GENL_FAMILY_TYPE_MPTCP_PM].name);
+        return -ENOSYS;
+    }
+
+    _LOGT("mptcp: %s address %s",
+          cmd_str,
+          nm_platform_mptcp_addr_to_string(addr, sbuf, sizeof(sbuf)));
+
+    nlmsg = nlmsg_alloc_size(nlmsg_total_size(GENL_HDRLEN) + 200);
+
+    if (!genlmsg_put(nlmsg,
+                     NL_AUTO_PORT,
+                     NL_AUTO_SEQ,
+                     genl_family_id,
+                     0,
+                     NLM_F_REQUEST,
+                     cmd_num,
+                     MPTCP_PM_VER))
+        goto nla_put_failure;
+
+    nla_nest = nla_nest_start(nlmsg, MPTCP_PM_ATTR_ADDR | NLA_F_NESTED);
+    if (!nla_nest)
+        goto nla_put_failure;
+
+    if (addr->id != 0)
+        NLA_PUT_U8(nlmsg, MPTCP_PM_ADDR_ATTR_ID, addr->id);
+    if (addr->flags != 0)
+        NLA_PUT_U32(nlmsg, MPTCP_PM_ADDR_ATTR_FLAGS, addr->flags);
+    if (addr->ifindex != 0)
+        NLA_PUT_S32(nlmsg, MPTCP_PM_ADDR_ATTR_IF_IDX, addr->ifindex);
+    if (addr->port != 0)
+        NLA_PUT_U16(nlmsg, MPTCP_PM_ADDR_ATTR_PORT, addr->port);
+    if (addr->addr_family != AF_UNSPEC) {
+        int addr_attr;
+
+        NLA_PUT_U16(nlmsg, MPTCP_PM_ADDR_ATTR_FAMILY, addr->addr_family);
+        if (addr->addr_family == AF_INET)
+            addr_attr = MPTCP_PM_ADDR_ATTR_ADDR4;
+        else if (addr->addr_family == AF_INET6)
+            addr_attr = MPTCP_PM_ADDR_ATTR_ADDR6;
+        else
+            g_return_val_if_reached(-NME_BUG);
+        NLA_PUT(nlmsg, addr_attr, nm_utils_addr_family_to_size(addr->addr_family), &addr->addr);
+    }
+
+    NLA_NEST_END(nlmsg, nla_nest);
+
+    nle = nl_send_auto(priv->sk_genl_sync, nlmsg);
+    if (nle < 0) {
+        _LOGT("mptcp: %s address %s: failed sending request: %s (%d)",
+              cmd_str,
+              nm_platform_mptcp_addr_to_string(addr, sbuf, sizeof(sbuf)),
+              nm_strerror(nle),
+              nle);
+        return nle;
+    }
+
+    do {
+        nle = nl_recvmsgs(priv->sk_genl_sync, &cb);
+    } while (nle == -EAGAIN);
+
+    if (nle < 0) {
+        _LOGT("mptcp: %s address %s: failed: %s (%d)%s%s%s",
+              cmd_str,
+              nm_platform_mptcp_addr_to_string(addr, sbuf, sizeof(sbuf)),
+              nm_strerror(nle),
+              nle,
+              NM_PRINT_FMT_QUOTED(extack_msg[0] != '\0', " \"", extack_msg, "\"", ""));
+        return nle;
+    }
+
+    _LOGT("mptcp: %s address %s: success",
+          cmd_str,
+          nm_platform_mptcp_addr_to_string(addr, sbuf, sizeof(sbuf)));
+
+    return 0;
+
+nla_put_failure:
+    g_return_val_if_reached(-NME_BUG);
+}
+
+static GPtrArray *
+mptcp_addrs_dump(NMPlatform *platform)
+{
+    NMLinuxPlatformPrivate      *priv  = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
+    gs_unref_ptrarray GPtrArray *addrs = NULL;
+    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+    FetchMptcpAddrParseData      parse_data;
+    guint16                      genl_family_id;
+    int                          r;
+    guint                        i;
+
+    genl_family_id = nm_platform_genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_MPTCP_PM);
+    if (genl_family_id == 0) {
+        _LOGT("mptcp: dump addresses fails because %s generic netlink family is unknown",
+              nmp_genl_family_infos[NMP_GENL_FAMILY_TYPE_MPTCP_PM].name);
+        return NULL;
+    }
+
+    nlmsg = nlmsg_alloc_size(nlmsg_total_size(GENL_HDRLEN));
+
+    if (!genlmsg_put(nlmsg,
+                     NL_AUTO_PORT,
+                     NL_AUTO_SEQ,
+                     genl_family_id,
+                     0,
+                     NLM_F_REQUEST | NLM_F_DUMP,
+                     MPTCP_PM_CMD_GET_ADDR,
+                     MPTCP_PM_VER))
+        g_return_val_if_reached(NULL);
+
+    r = nl_send_auto(priv->sk_genl_sync, nlmsg);
+    if (r < 0) {
+        _LOGT("mptcp: dump addresses failed to send dump request: %s", nm_strerror(r));
+        return NULL;
+    }
+
+    addrs = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+
+    parse_data = (FetchMptcpAddrParseData){
+        .addrs = addrs,
+    };
+
+    nl_recvmsgs(priv->sk_genl_sync,
+                &((const struct nl_cb){
+                    .valid_cb  = _mptcp_addrs_dump_parse_cb,
+                    .valid_arg = (gpointer) &parse_data,
+                }));
+
+    if (_LOGT_ENABLED()) {
+        _LOGT("mptcp: %u addresses dumped", addrs->len);
+        for (i = 0; i < addrs->len; i++) {
+            char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
+            _LOGT("mptcp: address[%04d]: %s",
+                  i,
+                  nmp_object_to_string(addrs->pdata[i],
+                                       NMP_OBJECT_TO_STRING_PUBLIC,
+                                       sbuf,
+                                       sizeof(sbuf)));
+        }
+    }
+
+    return g_steal_pointer(&addrs);
+}
+
+/*****************************************************************************/
+
 static void
 cache_update_link_udev(NMPlatform *platform, int ifindex, struct udev_device *udevice)
 {
@@ -9689,7 +10539,9 @@ nm_linux_platform_init(NMLinuxPlatform *self)
 
     priv->delayed_action.list_master_connected = g_ptr_array_new();
     priv->delayed_action.list_refresh_link     = g_ptr_array_new();
-    priv->delayed_action.list_wait_for_nl_response =
+    priv->delayed_action.list_wait_for_response_rtnl =
+        g_array_new(FALSE, TRUE, sizeof(DelayedActionWaitForNlResponseData));
+    priv->delayed_action.list_wait_for_response_genl =
         g_array_new(FALSE, TRUE, sizeof(DelayedActionWaitForNlResponseData));
 }
 
@@ -9721,43 +10573,53 @@ constructed(GObject *_object)
           nm_platform_get_use_udev(platform) ? "use" : "no",
           nm_platform_get_cache_tc(platform) ? "use" : "no");
 
-    priv->genl = nl_socket_alloc();
-    g_assert(priv->genl);
+    /*************************************************************************/
 
-    nle = nl_connect(priv->genl, NETLINK_GENERIC);
-    if (nle) {
-        _LOGE("unable to connect the generic netlink socket \"%s\" (%d)", nm_strerror(nle), -nle);
-        nl_socket_free(priv->genl);
-        priv->genl = NULL;
-    }
+    nle = nl_socket_new(&priv->sk_genl_sync, NETLINK_GENERIC, NL_SOCKET_FLAGS_NONE, 0, 0);
+    g_assert(!nle);
 
-    priv->nlh = nl_socket_alloc();
-    g_assert(priv->nlh);
+    _LOGD("genl: generic netlink socket for sync operations created: port=%u, fd=%d",
+          nl_socket_get_local_port(priv->sk_genl_sync),
+          nl_socket_get_fd(priv->sk_genl_sync));
 
-    nle = nl_connect(priv->nlh, NETLINK_ROUTE);
-    g_assert(!nle);
-    nle = nl_socket_set_passcred(priv->nlh, 1);
-    g_assert(!nle);
+    /*************************************************************************/
 
-    /* No blocking for event socket, so that we can drain it safely. */
-    nle = nl_socket_set_nonblocking(priv->nlh);
+    /* disable MSG_PEEK, we will handle lost messages ourselves. */
+    nle = nl_socket_new(&priv->sk_genl,
+                        NETLINK_GENERIC,
+                        NL_SOCKET_FLAGS_NONBLOCK | NL_SOCKET_FLAGS_PASSCRED
+                            | NL_SOCKET_FLAGS_DISABLE_MSG_PEEK,
+                        8 * 1024 * 1024,
+                        0);
     g_assert(!nle);
 
-    /* use 8 MB for receive socket kernel queue. */
-    nle = nl_socket_set_buffer_size(priv->nlh, 8 * 1024 * 1024, 0);
+    nle = nl_socket_add_memberships(priv->sk_genl, GENL_ID_CTRL, 0);
     g_assert(!nle);
 
-    nle = nl_socket_set_ext_ack(priv->nlh, TRUE);
-    if (nle)
-        _LOGD("could not enable extended acks on netlink socket");
+    fd = nl_socket_get_fd(priv->sk_genl);
 
-    /* explicitly set the msg buffer size and disable MSG_PEEK.
-     * We use our own receive buffer priv->netlink_recv_buf.
-     * If we encounter NME_NL_MSG_TRUNC, we will increase the buffer
-     * and resync (as we would have lost the message without NL_MSG_PEEK). */
-    nl_socket_disable_msg_peek(priv->nlh);
+    _LOGD("genl: generic netlink socket created: port=%u, fd=%d",
+          nl_socket_get_local_port(priv->sk_genl),
+          fd);
+
+    priv->event_source_genl =
+        nm_g_unix_fd_add_source(fd,
+                                G_IO_IN | G_IO_NVAL | G_IO_PRI | G_IO_ERR | G_IO_HUP,
+                                _nl_event_handler_genl,
+                                platform);
+
+    /*************************************************************************/
+
+    /* disable MSG_PEEK, we will handle lost messages ourselves. */
+    nle = nl_socket_new(&priv->sk_rtnl,
+                        NETLINK_ROUTE,
+                        NL_SOCKET_FLAGS_NONBLOCK | NL_SOCKET_FLAGS_PASSCRED
+                            | NL_SOCKET_FLAGS_DISABLE_MSG_PEEK,
+                        8 * 1024 * 1024,
+                        0);
+    g_assert(!nle);
 
-    nle = nl_socket_add_memberships(priv->nlh,
+    nle = nl_socket_add_memberships(priv->sk_rtnl,
                                     RTNLGRP_IPV4_IFADDR,
                                     RTNLGRP_IPV4_ROUTE,
                                     RTNLGRP_IPV4_RULE,
@@ -9769,37 +10631,32 @@ constructed(GObject *_object)
     g_assert(!nle);
 
     if (nm_platform_get_cache_tc(platform)) {
-        nle = nl_socket_add_memberships(priv->nlh, RTNLGRP_TC, 0);
+        nle = nl_socket_add_memberships(priv->sk_rtnl, RTNLGRP_TC, 0);
         nm_assert(!nle);
     }
 
-    fd = nl_socket_get_fd(priv->nlh);
+    fd = nl_socket_get_fd(priv->sk_rtnl);
 
-    _LOGD("Netlink socket for events established: port=%u, fd=%d",
-          nl_socket_get_local_port(priv->nlh),
+    _LOGD("rtnl: rtnetlink socket created: port=%u, fd=%d",
+          nl_socket_get_local_port(priv->sk_rtnl),
           fd);
 
-    priv->event_source =
+    priv->event_source_rtnl =
         nm_g_unix_fd_add_source(fd,
                                 G_IO_IN | G_IO_NVAL | G_IO_PRI | G_IO_ERR | G_IO_HUP,
-                                event_handler,
+                                _nl_event_handler_rtnl,
                                 platform);
 
+    /*************************************************************************/
+
     /* complete construction of the GObject instance before populating the cache. */
     G_OBJECT_CLASS(nm_linux_platform_parent_class)->constructed(_object);
 
     _LOGD("populate platform cache");
-    delayed_action_schedule(
-        platform,
-        DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_ROUTING_RULES_ALL
-            | DELAYED_ACTION_TYPE_REFRESH_ALL_QDISCS | DELAYED_ACTION_TYPE_REFRESH_ALL_TFILTERS,
-        NULL);
+    delayed_action_schedule_refresh_all(platform, NMP_NETLINK_ROUTE);
+    delayed_action_schedule_refresh_all(platform, NMP_NETLINK_GENERIC);
 
-    delayed_action_handle_all(platform, FALSE);
+    delayed_action_handle_all(platform);
 
     /* Set up udev monitoring */
     if (priv->udev_client) {
@@ -9881,6 +10738,11 @@ dispose(GObject *object)
     _LOGD("dispose");
 
     delayed_action_wait_for_nl_response_complete_all(platform,
+                                                     NMP_NETLINK_GENERIC,
+                                                     WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
+
+    delayed_action_wait_for_nl_response_complete_all(platform,
+                                                     NMP_NETLINK_ROUTE,
                                                      WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
 
     priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE;
@@ -9897,13 +10759,15 @@ finalize(GObject *object)
 
     g_ptr_array_unref(priv->delayed_action.list_master_connected);
     g_ptr_array_unref(priv->delayed_action.list_refresh_link);
-    g_array_unref(priv->delayed_action.list_wait_for_nl_response);
+    g_array_unref(priv->delayed_action.list_wait_for_response_rtnl);
+    g_array_unref(priv->delayed_action.list_wait_for_response_genl);
 
-    nl_socket_free(priv->genl);
+    nm_clear_g_source_inst(&priv->event_source_genl);
+    nm_clear_g_source_inst(&priv->event_source_rtnl);
 
-    nm_clear_g_source_inst(&priv->event_source);
-
-    nl_socket_free(priv->nlh);
+    nl_socket_free(priv->sk_genl_sync);
+    nl_socket_free(priv->sk_genl);
+    nl_socket_free(priv->sk_rtnl);
 
     {
         NM_G_MUTEX_LOCKED(&sysctl_clear_cache_lock);
@@ -10021,4 +10885,8 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass)
     platform_class->tfilter_delete = tfilter_delete;
 
     platform_class->process_events = process_events;
+
+    platform_class->genl_get_family_id = genl_get_family_id;
+    platform_class->mptcp_addr_update  = mptcp_addr_update;
+    platform_class->mptcp_addrs_dump   = mptcp_addrs_dump;
 }