about summary refs log tree commit diff
path: root/src/libnm-platform
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2025-02-12 13:46:50 +0100
committerMichael Biebl <biebl@debian.org>2025-02-12 13:46:50 +0100
commit8bdf070ff046f482f6eb5e2b15ebc216f5d1e3da (patch)
treef706478d189d54c6532e8863d4b0d5ff5575af60 /src/libnm-platform
parent818258cf34b83fbc754633295e1052d4752d7b15 (diff)
New upstream version 1.51.90 upstream/1.51.90
Diffstat (limited to 'src/libnm-platform')
-rw-r--r--src/libnm-platform/nm-linux-platform.c81
-rw-r--r--src/libnm-platform/nm-netlink.c4
-rw-r--r--src/libnm-platform/nm-netlink.h21
-rw-r--r--src/libnm-platform/nm-platform-utils.c72
-rw-r--r--src/libnm-platform/nm-platform-utils.h4
-rw-r--r--src/libnm-platform/nm-platform.c84
-rw-r--r--src/libnm-platform/nm-platform.h46
-rw-r--r--src/libnm-platform/nmp-base.h1
-rw-r--r--src/libnm-platform/nmp-global-tracker.c22
-rw-r--r--src/libnm-platform/nmp-netns.c2
-rw-r--r--src/libnm-platform/nmp-object.c12
-rw-r--r--src/libnm-platform/nmp-object.h8
-rw-r--r--src/libnm-platform/nmp-plobj.h13
-rw-r--r--src/libnm-platform/tests/test-nm-platform.c68
-rw-r--r--src/libnm-platform/wifi/nm-wifi-utils-nl80211.c2
15 files changed, 342 insertions, 98 deletions
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index f348e27c..24109568 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -865,6 +865,7 @@ static const LinkDesc link_descs[] = {
     [NM_LINK_TYPE_IP6GRE]      = {"ip6gre", "ip6gre", NULL},
     [NM_LINK_TYPE_IP6GRETAP]   = {"ip6gretap", "ip6gretap", NULL},
     [NM_LINK_TYPE_IPIP]        = {"ipip", "ipip", NULL},
+    [NM_LINK_TYPE_IPVLAN]      = {"ipvlan", "ipvlan", NULL},
     [NM_LINK_TYPE_LOOPBACK]    = {"loopback", NULL, NULL},
     [NM_LINK_TYPE_MACSEC]      = {"macsec", "macsec", NULL},
     [NM_LINK_TYPE_MACVLAN]     = {"macvlan", "macvlan", NULL},
@@ -912,6 +913,7 @@ _link_type_from_rtnl_type(const char *name)
         NM_LINK_TYPE_IP6GRETAP,   /* "ip6gretap"   */
         NM_LINK_TYPE_IP6TNL,      /* "ip6tnl"      */
         NM_LINK_TYPE_IPIP,        /* "ipip"        */
+        NM_LINK_TYPE_IPVLAN,      /* "ipvlan"      */
         NM_LINK_TYPE_MACSEC,      /* "macsec"      */
         NM_LINK_TYPE_MACVLAN,     /* "macvlan"     */
         NM_LINK_TYPE_MACVTAP,     /* "macvtap"     */
@@ -2111,6 +2113,40 @@ _parse_lnk_ipip(const char *kind, struct nlattr *info_data)
 /*****************************************************************************/
 
 static NMPObject *
+_parse_lnk_ipvlan(const char *kind, struct nlattr *info_data)
+{
+    static const struct nla_policy policy[] = {
+        [IFLA_IPVLAN_MODE]  = {.type = NLA_U16},
+        [IFLA_IPVLAN_FLAGS] = {.type = NLA_U16},
+    };
+    NMPlatformLnkIpvlan *props;
+    struct nlattr       *tb[G_N_ELEMENTS(policy)];
+    NMPObject           *obj;
+
+    if (!info_data || !kind)
+        return NULL;
+
+    if (nla_parse_nested_arr(tb, info_data, policy) < 0)
+        return NULL;
+
+    if (!tb[IFLA_IPVLAN_MODE])
+        return NULL;
+
+    obj         = nmp_object_new(NMP_OBJECT_TYPE_LNK_IPVLAN, NULL);
+    props       = &obj->lnk_ipvlan;
+    props->mode = nla_get_u16(tb[IFLA_IPVLAN_MODE]);
+
+    if (tb[IFLA_IPVLAN_FLAGS]) {
+        props->private_flag = NM_FLAGS_HAS(nla_get_u16(tb[IFLA_IPVLAN_FLAGS]), IPVLAN_F_PRIVATE);
+        props->vepa         = NM_FLAGS_HAS(nla_get_u16(tb[IFLA_IPVLAN_FLAGS]), IPVLAN_F_VEPA);
+    }
+
+    return obj;
+}
+
+/*****************************************************************************/
+
+static NMPObject *
 _parse_lnk_macvlan(const char *kind, struct nlattr *info_data)
 {
     static const struct nla_policy policy[] = {
@@ -2678,7 +2714,7 @@ _wireguard_update_from_allowed_ips_nla(NMPWireGuardAllowedIP *allowed_ip, struct
 
     _check_addr_or_return_val(tb, WGALLOWEDIP_A_IPADDR, addr_len, FALSE);
 
-    *allowed_ip = (NMPWireGuardAllowedIP){
+    *allowed_ip = (NMPWireGuardAllowedIP) {
         .family = family,
     };
 
@@ -2928,7 +2964,7 @@ _wireguard_read_info(NMPlatform     *platform /* used only as logging context */
     /* we ignore errors, and return whatever we could successfully
      * parse. */
     nl_recvmsgs(genl,
-                &((const struct nl_cb){
+                &((const struct nl_cb) {
                     .valid_cb  = _wireguard_get_device_cb,
                     .valid_arg = (gpointer) &parse_data,
                 }));
@@ -3365,7 +3401,7 @@ link_wireguard_change(NMPlatform                               *platform,
 static void
 _nmp_link_address_set(NMPLinkAddress *dst, const struct nlattr *nla)
 {
-    *dst = (NMPLinkAddress){
+    *dst = (NMPLinkAddress) {
         .len = 0,
     };
     if (nla) {
@@ -3674,6 +3710,9 @@ _new_from_nl_link(NMPlatform            *platform,
     case NM_LINK_TYPE_IPIP:
         lnk_data = _parse_lnk_ipip(nl_info_kind, nl_info_data);
         break;
+    case NM_LINK_TYPE_IPVLAN:
+        lnk_data = _parse_lnk_ipvlan(nl_info_kind, nl_info_data);
+        break;
     case NM_LINK_TYPE_MACSEC:
         lnk_data = _parse_lnk_macsec(nl_info_kind, nl_info_data);
         break;
@@ -5252,6 +5291,26 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo
         NLA_PUT_U8(msg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
         break;
     }
+    case NM_LINK_TYPE_IPVLAN:
+    {
+        const NMPlatformLnkIpvlan *props = extra_data;
+        guint16                    flags = 0;
+
+        nm_assert(props);
+
+        if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
+            goto nla_put_failure;
+
+        if (props->private_flag)
+            flags |= IPVLAN_F_PRIVATE;
+
+        if (props->vepa)
+            flags |= IPVLAN_F_VEPA;
+
+        NLA_PUT_U16(msg, IFLA_IPVLAN_MODE, props->mode);
+        NLA_PUT_U16(msg, IFLA_IPVLAN_FLAGS, flags);
+        break;
+    }
     case NM_LINK_TYPE_MACSEC:
     {
         const NMPlatformLnkMacsec *props = extra_data;
@@ -8156,7 +8215,7 @@ _rtnl_handle_msg(NMPlatform *platform, const struct nl_msg_lite *msg)
         is_del = TRUE;
     }
 
-    parse_nlmsg_iter = (ParseNlmsgIter){
+    parse_nlmsg_iter = (ParseNlmsgIter) {
         .iter_more = FALSE,
     };
 
@@ -9674,13 +9733,13 @@ link_get_bridge_vlans(NMPlatform            *platform,
         goto err;
     }
 
-    data = ((BridgeVlanData){
+    data = ((BridgeVlanData) {
         .ifindex = ifindex,
     });
 
     do {
         nle = nl_recvmsgs(sk,
-                          &((const struct nl_cb){
+                          &((const struct nl_cb) {
                               .valid_cb  = get_bridge_vlans_cb,
                               .valid_arg = &data,
                           }));
@@ -10655,6 +10714,7 @@ static int
 ip_route_get(NMPlatform   *platform,
              int           addr_family,
              gconstpointer address,
+             guint32       fwmark,
              int           oif_ifindex,
              NMPObject   **out_route)
 {
@@ -10689,6 +10749,11 @@ ip_route_get(NMPlatform   *platform,
         if (!_nl_addattr_l(&req.n, sizeof(req), RTA_DST, address, addr_len))
             nm_assert_not_reached();
 
+        if (fwmark != 0) {
+            if (!_nl_addattr_l(&req.n, sizeof(req), RTA_MARK, &fwmark, sizeof(fwmark)))
+                nm_assert_not_reached();
+        }
+
         if (oif_ifindex > 0) {
             gint32 ii = oif_ifindex;
 
@@ -11726,12 +11791,12 @@ mptcp_addrs_dump(NMPlatform *platform)
 
     addrs = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
 
-    parse_data = (FetchMptcpAddrParseData){
+    parse_data = (FetchMptcpAddrParseData) {
         .addrs = addrs,
     };
 
     nl_recvmsgs(priv->sk_genl_sync,
-                &((const struct nl_cb){
+                &((const struct nl_cb) {
                     .valid_cb  = _mptcp_addrs_dump_parse_cb,
                     .valid_arg = (gpointer) &parse_data,
                 }));
diff --git a/src/libnm-platform/nm-netlink.c b/src/libnm-platform/nm-netlink.c
index 5bbbcc84..9eb6721f 100644
--- a/src/libnm-platform/nm-netlink.c
+++ b/src/libnm-platform/nm-netlink.c
@@ -393,7 +393,7 @@ nlmsg_alloc(size_t len)
         g_return_val_if_reached(NULL);
 
     nm  = g_slice_new(struct nl_msg);
-    *nm = (struct nl_msg){
+    *nm = (struct nl_msg) {
         .nm_protocol = -1,
         .nm_size     = len,
         .nm_nlh      = g_malloc0(len),
@@ -1125,7 +1125,7 @@ nl_socket_new(struct nl_sock **out_sk,
     nm_random_get_bytes(&seq_init, sizeof(seq_init));
 
     sk  = g_slice_new(struct nl_sock);
-    *sk = (struct nl_sock){
+    *sk = (struct nl_sock) {
         .s_fd = nm_steal_fd(&fd),
         .s_local =
             {
diff --git a/src/libnm-platform/nm-netlink.h b/src/libnm-platform/nm-netlink.h
index efd482ad..42a81d84 100644
--- a/src/libnm-platform/nm-netlink.h
+++ b/src/libnm-platform/nm-netlink.h
@@ -93,19 +93,20 @@ struct nla_policy {
 
 /* static asserts that @tb and @policy are suitable arguments to nla_parse(). */
 #if _NM_CC_SUPPORT_GENERIC
-#define _nl_static_assert_tb(tb, policy)                                                 \
-    G_STMT_START                                                                         \
-    {                                                                                    \
-        G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(tb) > 0);                                      \
-                                                                                         \
+#define _nl_static_assert_tb(tb, policy)                                                     \
+    G_STMT_START                                                                             \
+    {                                                                                        \
+        G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(tb) > 0);                                          \
+                                                                                             \
         /* We allow @policy to be either a C array or NULL. The sizeof()
          * must either match the expected array size or we check that
          * "policy" has typeof(NULL). This isn't a perfect compile time check,
-         * but good enough. */                 \
-        G_STATIC_ASSERT_EXPR(_Generic((policy),                                          \
-            typeof(NULL): 1,                                                             \
-            default: (sizeof(policy) == G_N_ELEMENTS(tb) * sizeof(struct nla_policy)))); \
-    }                                                                                    \
+         * but good enough. */                     \
+        G_STATIC_ASSERT_EXPR(                                                                \
+            _Generic((policy),                                                               \
+                typeof(NULL): 1,                                                             \
+                default: (sizeof(policy) == G_N_ELEMENTS(tb) * sizeof(struct nla_policy)))); \
+    }                                                                                        \
     G_STMT_END
 #else
 #define _nl_static_assert_tb(tb, policy) G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(tb) > 0)
diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c
index 3f70f5fe..15aac11c 100644
--- a/src/libnm-platform/nm-platform-utils.c
+++ b/src/libnm-platform/nm-platform-utils.c
@@ -431,11 +431,11 @@ ethtool_get_stringset_index(SocketHandle *shandle, int stringset_id, const char
 /*****************************************************************************/
 
 static const NMEthtoolFeatureInfo _ethtool_feature_infos[_NM_ETHTOOL_ID_FEATURE_NUM] = {
-#define ETHT_FEAT(eid, ...)                                     \
-    {                                                           \
-        .ethtool_id     = eid,                                  \
-        .n_kernel_names = NM_NARG(__VA_ARGS__),                 \
-        .kernel_names   = ((const char *const[]){__VA_ARGS__}), \
+#define ETHT_FEAT(eid, ...)                                      \
+    {                                                            \
+        .ethtool_id     = eid,                                   \
+        .n_kernel_names = NM_NARG(__VA_ARGS__),                  \
+        .kernel_names   = ((const char *const[]) {__VA_ARGS__}), \
     }
 
     /* the order does only matter for one thing: if it happens that more than one NMEthtoolID
@@ -882,7 +882,7 @@ nmp_utils_ethtool_get_coalesce(int ifindex, NMEthtoolCoalesceState *coalesce)
         return FALSE;
     }
 
-    *coalesce = (NMEthtoolCoalesceState){
+    *coalesce = (NMEthtoolCoalesceState) {
         .s = {
             [_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)] =
                 eth_data.rx_coalesce_usecs,
@@ -946,7 +946,7 @@ nmp_utils_ethtool_set_coalesce(int ifindex, const NMEthtoolCoalesceState *coales
     g_return_val_if_fail(ifindex > 0, FALSE);
     g_return_val_if_fail(coalesce, FALSE);
 
-    eth_data = (struct ethtool_coalesce){
+    eth_data = (struct ethtool_coalesce) {
         .cmd = ETHTOOL_SCOALESCE,
         .rx_coalesce_usecs =
             coalesce->s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(NM_ETHTOOL_ID_COALESCE_RX_USECS)],
@@ -1027,7 +1027,7 @@ nmp_utils_ethtool_get_ring(int ifindex, NMEthtoolRingState *ring)
         return FALSE;
     }
 
-    *ring = (NMEthtoolRingState){
+    *ring = (NMEthtoolRingState) {
         .rx_pending       = eth_data.rx_pending,
         .rx_jumbo_pending = eth_data.rx_jumbo_pending,
         .rx_mini_pending  = eth_data.rx_mini_pending,
@@ -1049,7 +1049,7 @@ nmp_utils_ethtool_set_ring(int ifindex, const NMEthtoolRingState *ring)
     g_return_val_if_fail(ifindex > 0, FALSE);
     g_return_val_if_fail(ring, FALSE);
 
-    eth_data = (struct ethtool_ringparam){
+    eth_data = (struct ethtool_ringparam) {
         .cmd              = ETHTOOL_SRINGPARAM,
         .rx_pending       = ring->rx_pending,
         .rx_jumbo_pending = ring->rx_jumbo_pending,
@@ -1087,7 +1087,7 @@ nmp_utils_ethtool_get_channels(int ifindex, NMEthtoolChannelsState *channels)
         return FALSE;
     }
 
-    *channels = (NMEthtoolChannelsState){
+    *channels = (NMEthtoolChannelsState) {
         .rx       = eth_data.rx_count,
         .tx       = eth_data.tx_count,
         .other    = eth_data.other_count,
@@ -1109,7 +1109,7 @@ nmp_utils_ethtool_set_channels(int ifindex, const NMEthtoolChannelsState *channe
     g_return_val_if_fail(ifindex > 0, FALSE);
     g_return_val_if_fail(channels, FALSE);
 
-    eth_data = (struct ethtool_channels){
+    eth_data = (struct ethtool_channels) {
         .cmd            = ETHTOOL_SCHANNELS,
         .rx_count       = channels->rx,
         .tx_count       = channels->tx,
@@ -1150,7 +1150,7 @@ nmp_utils_ethtool_get_pause(int ifindex, NMEthtoolPauseState *pause)
         return FALSE;
     }
 
-    *pause = (NMEthtoolPauseState){
+    *pause = (NMEthtoolPauseState) {
         .autoneg = eth_data.autoneg == 1,
         .rx      = eth_data.rx_pause == 1,
         .tx      = eth_data.tx_pause == 1,
@@ -1181,7 +1181,7 @@ nmp_utils_ethtool_get_eee(int ifindex, NMEthtoolEEEState *eee)
         return FALSE;
     }
 
-    *eee = (NMEthtoolEEEState){
+    *eee = (NMEthtoolEEEState) {
         .enabled = eth_data.eee_enabled == 1,
     };
 
@@ -1201,7 +1201,7 @@ nmp_utils_ethtool_set_pause(int ifindex, const NMEthtoolPauseState *pause)
     g_return_val_if_fail(ifindex > 0, FALSE);
     g_return_val_if_fail(pause, FALSE);
 
-    eth_data = (struct ethtool_pauseparam){
+    eth_data = (struct ethtool_pauseparam) {
         .cmd      = ETHTOOL_SPAUSEPARAM,
         .autoneg  = pause->autoneg ? 1 : 0,
         .rx_pause = pause->rx ? 1 : 0,
@@ -1271,7 +1271,7 @@ nmp_utils_ethtool_get_driver_info(int ifindex, NMPUtilsEthtoolDriverInfo *data)
     g_return_val_if_fail(data, FALSE);
 
     drvinfo  = (struct ethtool_drvinfo *) data;
-    *drvinfo = (struct ethtool_drvinfo){
+    *drvinfo = (struct ethtool_drvinfo) {
         .cmd = ETHTOOL_GDRVINFO,
     };
     return _ethtool_call_once(ifindex, drvinfo, sizeof(*drvinfo)) >= 0;
@@ -1609,7 +1609,7 @@ set_link_settings_new(SocketHandle            *shandle,
     guint                                 nwords;
     guint                                 i;
 
-    edata0 = (struct ethtool_link_settings){
+    edata0 = (struct ethtool_link_settings) {
         .cmd                    = ETHTOOL_GLINKSETTINGS,
         .link_mode_masks_nwords = 0,
     };
@@ -1820,6 +1820,46 @@ nmp_utils_ethtool_set_wake_on_lan(int                      ifindex,
     return _ethtool_call_once(ifindex, &wol_info, sizeof(wol_info)) >= 0;
 }
 
+gboolean
+nmp_utils_ethtool_get_fec_mode(int ifindex, uint32_t *fec_mode)
+{
+    int                     r;
+    struct ethtool_fecparam fec_param = {
+        .cmd = ETHTOOL_GFECPARAM,
+        .fec = 0,
+    };
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    if (_ethtool_call_once(ifindex, &fec_param, sizeof(fec_param)) >= 0) {
+        nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: get FEC options 0x%x", ifindex, fec_param.fec);
+        *fec_mode = fec_param.fec;
+        return TRUE;
+    } else {
+        r = -NM_ERRNO_NATIVE(errno);
+        nm_log_dbg(LOGD_PLATFORM,
+                   "ethtool[%d]: ETHTOOL_GFECPARAM failure get fec mode: (%s)",
+                   ifindex,
+                   nm_strerror_native(-r));
+        return FALSE;
+    }
+}
+
+gboolean
+nmp_utils_ethtool_set_fec_mode(int ifindex, uint32_t fec_mode)
+{
+    struct ethtool_fecparam fec_param = {
+        .cmd = ETHTOOL_SFECPARAM,
+        .fec = fec_mode,
+    };
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    nm_log_dbg(LOGD_PLATFORM, "ethtool[%d]: setting FEC options 0x%x", ifindex, fec_mode);
+
+    return _ethtool_call_once(ifindex, &fec_param, sizeof(fec_param)) >= 0;
+}
+
 /******************************************************************************
  * mii
  *****************************************************************************/
diff --git a/src/libnm-platform/nm-platform-utils.h b/src/libnm-platform/nm-platform-utils.h
index 96ac22ef..f47bcd49 100644
--- a/src/libnm-platform/nm-platform-utils.h
+++ b/src/libnm-platform/nm-platform-utils.h
@@ -66,6 +66,10 @@ gboolean nmp_utils_ethtool_get_eee(int ifindex, NMEthtoolEEEState *eee);
 
 gboolean nmp_utils_ethtool_set_eee(int ifindex, const NMEthtoolEEEState *eee);
 
+gboolean nmp_utils_ethtool_get_fec_mode(int ifindex, uint32_t *fec_mode);
+
+gboolean nmp_utils_ethtool_set_fec_mode(int ifindex, uint32_t fec_mode);
+
 /*****************************************************************************/
 
 gboolean nmp_utils_mii_supports_carrier_detect(int ifindex);
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index 658efadb..a8d34b71 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -1420,6 +1420,12 @@ nm_platform_link_add(NMPlatform            *self,
                                                   buf_p,
                                                   buf_len);
                    break;
+               case NM_LINK_TYPE_IPVLAN:
+                   nm_strbuf_append_str(&buf_p, &buf_len, ", ");
+                   nm_platform_lnk_ipvlan_to_string((const NMPlatformLnkIpvlan *) extra_data,
+                                                    buf_p,
+                                                    buf_len);
+                   break;
                case NM_LINK_TYPE_MACSEC:
                    nm_strbuf_append_str(&buf_p, &buf_len, ", ");
                    nm_platform_lnk_macsec_to_string((const NMPlatformLnkMacsec *) extra_data,
@@ -2606,6 +2612,12 @@ nm_platform_link_get_lnk_ipip(NMPlatform *self, int ifindex, const NMPlatformLin
     return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IPIP, out_link);
 }
 
+const NMPlatformLnkIpvlan *
+nm_platform_link_get_lnk_ipvlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
+{
+    return _link_get_lnk(self, ifindex, NM_LINK_TYPE_IPVLAN, out_link);
+}
+
 const NMPlatformLnkMacsec *
 nm_platform_link_get_lnk_macsec(NMPlatform *self, int ifindex, const NMPlatformLink **out_link)
 {
@@ -3608,6 +3620,26 @@ nm_platform_ethtool_set_features(
 }
 
 gboolean
+nm_platform_ethtool_get_fec_mode(NMPlatform *self, int ifindex, uint32_t *fec_mode)
+{
+    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    return nmp_utils_ethtool_get_fec_mode(ifindex, fec_mode);
+}
+
+gboolean
+nm_platform_ethtool_set_fec_mode(NMPlatform *self, int ifindex, uint32_t fec_mode)
+{
+    _CHECK_SELF_NETNS(self, klass, netns, FALSE);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    return nmp_utils_ethtool_set_fec_mode(ifindex, fec_mode);
+}
+
+gboolean
 nm_platform_ethtool_get_link_coalesce(NMPlatform             *self,
                                       int                     ifindex,
                                       NMEthtoolCoalesceState *coalesce)
@@ -3814,7 +3846,7 @@ nm_platform_ip4_address_add(NMPlatform *self,
         char                 sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
         NMPlatformIP4Address addr;
 
-        addr = (NMPlatformIP4Address){
+        addr = (NMPlatformIP4Address) {
             .ifindex           = ifindex,
             .address           = address,
             .peer_address      = peer_address,
@@ -4813,7 +4845,7 @@ nm_platform_ip_address_get_prune_list(NMPlatform            *self,
             const NMPlatformIP4Address *a4 = NMP_OBJECT_CAST_IP4_ADDRESS(obj);
 
             if (a4->address == NM_IPV4LO_ADDR1 && a4->plen == NM_IPV4LO_PREFIXLEN) {
-                const NMPlatformIP4Address addr = (NMPlatformIP4Address){
+                const NMPlatformIP4Address addr = (NMPlatformIP4Address) {
                     .ifindex                   = NM_LOOPBACK_IFINDEX,
                     .address                   = NM_IPV4LO_ADDR1,
                     .peer_address              = NM_IPV4LO_ADDR1,
@@ -4963,7 +4995,7 @@ nm_platform_ip_route_get_prune_list(NMPlatform            *self,
                     NMPlatformIP4Route r;
 
                     if (rt->r4.network == NM_IPV4LO_ADDR1) {
-                        r = (NMPlatformIP4Route){
+                        r = (NMPlatformIP4Route) {
                             .ifindex       = NM_LOOPBACK_IFINDEX,
                             .type_coerced  = nm_platform_route_type_coerce(RTN_LOCAL),
                             .table_coerced = nm_platform_route_table_coerce(local_table),
@@ -4975,7 +5007,7 @@ nm_platform_ip_route_get_prune_list(NMPlatform            *self,
                             .pref_src      = NM_IPV4LO_ADDR1,
                         };
                     } else {
-                        r = (NMPlatformIP4Route){
+                        r = (NMPlatformIP4Route) {
                             .ifindex       = NM_LOOPBACK_IFINDEX,
                             .type_coerced  = nm_platform_route_type_coerce(RTN_LOCAL),
                             .table_coerced = nm_platform_route_table_coerce(local_table),
@@ -5624,6 +5656,7 @@ int
 nm_platform_ip_route_get(NMPlatform   *self,
                          int           addr_family,
                          gconstpointer address /* in_addr_t or struct in6_addr */,
+                         guint32       fwmark,
                          int           oif_ifindex,
                          NMPObject   **out_route)
 {
@@ -5632,21 +5665,23 @@ nm_platform_ip_route_get(NMPlatform   *self,
     int                       result;
     char                      buf[NM_INET_ADDRSTRLEN];
     char                      buf_oif[64];
+    char                      buf_fwmark[64];
 
     _CHECK_SELF(self, klass, FALSE);
 
     g_return_val_if_fail(address, -NME_BUG);
     g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), -NME_BUG);
 
-    _LOGT("route: get IPv%c route for: %s%s",
+    _LOGT("route: get IPv%c route for: %s%s%s",
           nm_utils_addr_family_to_char(addr_family),
           inet_ntop(addr_family, address, buf, sizeof(buf)),
-          oif_ifindex > 0 ? nm_sprintf_buf(buf_oif, " oif %d", oif_ifindex) : "");
+          oif_ifindex > 0 ? nm_sprintf_buf(buf_oif, " oif %d", oif_ifindex) : "",
+          fwmark > 0 ? nm_sprintf_buf(buf_fwmark, " fwmark %u", fwmark) : "");
 
     if (!klass->ip_route_get)
         result = -NME_PL_OPNOTSUPP;
     else {
-        result = klass->ip_route_get(self, addr_family, address, oif_ifindex, &route);
+        result = klass->ip_route_get(self, addr_family, address, fwmark, oif_ifindex, &route);
     }
 
     if (result < 0) {
@@ -6704,6 +6739,21 @@ nm_platform_lnk_macvlan_to_string(const NMPlatformLnkMacvlan *lnk, char *buf, gs
 }
 
 const char *
+nm_platform_lnk_ipvlan_to_string(const NMPlatformLnkIpvlan *lnk, char *buf, gsize len)
+{
+    if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
+        return buf;
+
+    g_snprintf(buf,
+               len,
+               "mode %u%s%s",
+               lnk->mode,
+               lnk->private_flag ? " private" : "",
+               lnk->vepa ? " vepa" : "");
+    return buf;
+}
+
+const char *
 nm_platform_lnk_sit_to_string(const NMPlatformLnkSit *lnk, char *buf, gsize len)
 {
     char str_local[30];
@@ -8591,6 +8641,22 @@ nm_platform_lnk_macvlan_cmp(const NMPlatformLnkMacvlan *a, const NMPlatformLnkMa
 }
 
 void
+nm_platform_lnk_ipvlan_hash_update(const NMPlatformLnkIpvlan *obj, NMHashState *h)
+{
+    nm_hash_update_vals(h, obj->mode, NM_HASH_COMBINE_BOOLS(guint8, obj->private_flag, obj->vepa));
+}
+
+int
+nm_platform_lnk_ipvlan_cmp(const NMPlatformLnkIpvlan *a, const NMPlatformLnkIpvlan *b)
+{
+    NM_CMP_SELF(a, b);
+    NM_CMP_FIELD(a, b, mode);
+    NM_CMP_FIELD_UNSAFE(a, b, private_flag);
+    NM_CMP_FIELD_UNSAFE(a, b, vepa);
+    return 0;
+}
+
+void
 nm_platform_lnk_sit_hash_update(const NMPlatformLnkSit *obj, NMHashState *h)
 {
     nm_hash_update_vals(h,
@@ -9652,7 +9718,7 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr,
         return NULL;
     }
 
-    *dst = (NMPlatformIP4Route){
+    *dst = (NMPlatformIP4Route) {
         .ifindex       = ifindex,
         .rt_source     = NM_IP_CONFIG_SOURCE_KERNEL,
         .network       = network_4,
@@ -9999,7 +10065,7 @@ nm_platform_ip6_dadfailed_set(NMPlatform            *self,
 
     if (failed) {
         addr  = g_slice_new(IP6DadFailedAddr);
-        *addr = (IP6DadFailedAddr){
+        *addr = (IP6DadFailedAddr) {
             .address        = *ip6,
             .ifindex        = ifindex,
             .timestamp_nsec = now_nsec,
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index 22bf0fdb..1c8a26b3 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -529,9 +529,9 @@ typedef union {
 
 #undef __NMPlatformIPRoute_COMMON
 
-#define NM_PLATFORM_IP4_ROUTE_INIT(...) (&((const NMPlatformIP4Route){__VA_ARGS__}))
+#define NM_PLATFORM_IP4_ROUTE_INIT(...) (&((const NMPlatformIP4Route) {__VA_ARGS__}))
 
-#define NM_PLATFORM_IP6_ROUTE_INIT(...) (&((const NMPlatformIP6Route){__VA_ARGS__}))
+#define NM_PLATFORM_IP6_ROUTE_INIT(...) (&((const NMPlatformIP6Route) {__VA_ARGS__}))
 
 typedef struct {
     /* struct fib_rule_uid_range */
@@ -880,6 +880,12 @@ typedef struct {
 } _nm_alignas(NMPlatformObject) NMPlatformLnkIpIp;
 
 typedef struct {
+    guint16 mode;
+    bool    private_flag : 1;
+    bool    vepa : 1;
+} _nm_alignas(NMPlatformObject) NMPlatformLnkIpvlan;
+
+typedef struct {
     int       parent_ifindex;
     in_addr_t local;
     in_addr_t remote;
@@ -1315,6 +1321,7 @@ typedef struct {
     int (*ip_route_get)(NMPlatform   *self,
                         int           addr_family,
                         gconstpointer address,
+                        guint32       fwmark,
                         int           oif_ifindex,
                         NMPObject   **out_route);
 
@@ -1935,6 +1942,27 @@ nm_platform_link_macvlan_add(NMPlatform                 *self,
                                 out_link);
 }
 
+static inline int
+nm_platform_link_ipvlan_add(NMPlatform                *self,
+                            const char                *name,
+                            int                        parent,
+                            const NMPlatformLnkIpvlan *props,
+                            const NMPlatformLink     **out_link)
+{
+    g_return_val_if_fail(props, -NME_BUG);
+    g_return_val_if_fail(parent > 0, -NME_BUG);
+
+    return nm_platform_link_add(self,
+                                NM_LINK_TYPE_IPVLAN,
+                                name,
+                                parent,
+                                NULL,
+                                0,
+                                0,
+                                props,
+                                out_link);
+}
+
 gboolean nm_platform_link_delete(NMPlatform *self, int ifindex);
 
 gboolean nm_platform_link_set_netns(NMPlatform *self, int ifindex, int netns_fd);
@@ -2117,6 +2145,8 @@ const NMPlatformLnkMacsec *
 nm_platform_link_get_lnk_macsec(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkMacvlan *
 nm_platform_link_get_lnk_macvlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
+const NMPlatformLnkIpvlan *
+nm_platform_link_get_lnk_ipvlan(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkMacvlan *
 nm_platform_link_get_lnk_macvtap(NMPlatform *self, int ifindex, const NMPlatformLink **out_link);
 const NMPlatformLnkSit *
@@ -2404,6 +2434,7 @@ gboolean nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifind
 int nm_platform_ip_route_get(NMPlatform   *self,
                              int           addr_family,
                              gconstpointer address,
+                             guint32       fwmark,
                              int           oif_ifindex,
                              NMPObject   **out_route);
 
@@ -2432,6 +2463,7 @@ const char *nm_platform_lnk_ipip_to_string(const NMPlatformLnkIpIp *lnk, char *b
 const char *nm_platform_lnk_macsec_to_string(const NMPlatformLnkMacsec *lnk, char *buf, gsize len);
 const char *
 nm_platform_lnk_macvlan_to_string(const NMPlatformLnkMacvlan *lnk, char *buf, gsize len);
+const char *nm_platform_lnk_ipvlan_to_string(const NMPlatformLnkIpvlan *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_sit_to_string(const NMPlatformLnkSit *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_tun_to_string(const NMPlatformLnkTun *lnk, char *buf, gsize len);
 const char *nm_platform_lnk_vlan_to_string(const NMPlatformLnkVlan *lnk, char *buf, gsize len);
@@ -2484,6 +2516,7 @@ int nm_platform_lnk_infiniband_cmp(const NMPlatformLnkInfiniband *a,
 int nm_platform_lnk_ip6tnl_cmp(const NMPlatformLnkIp6Tnl *a, const NMPlatformLnkIp6Tnl *b);
 int nm_platform_lnk_ipip_cmp(const NMPlatformLnkIpIp *a, const NMPlatformLnkIpIp *b);
 int nm_platform_lnk_macsec_cmp(const NMPlatformLnkMacsec *a, const NMPlatformLnkMacsec *b);
+int nm_platform_lnk_ipvlan_cmp(const NMPlatformLnkIpvlan *a, const NMPlatformLnkIpvlan *b);
 int nm_platform_lnk_macvlan_cmp(const NMPlatformLnkMacvlan *a, const NMPlatformLnkMacvlan *b);
 int nm_platform_lnk_sit_cmp(const NMPlatformLnkSit *a, const NMPlatformLnkSit *b);
 int nm_platform_lnk_tun_cmp(const NMPlatformLnkTun *a, const NMPlatformLnkTun *b);
@@ -2558,6 +2591,7 @@ void nm_platform_lnk_ip6tnl_hash_update(const NMPlatformLnkIp6Tnl *obj, NMHashSt
 void nm_platform_lnk_ipip_hash_update(const NMPlatformLnkIpIp *obj, NMHashState *h);
 void nm_platform_lnk_macsec_hash_update(const NMPlatformLnkMacsec *obj, NMHashState *h);
 void nm_platform_lnk_macvlan_hash_update(const NMPlatformLnkMacvlan *obj, NMHashState *h);
+void nm_platform_lnk_ipvlan_hash_update(const NMPlatformLnkIpvlan *obj, NMHashState *h);
 void nm_platform_lnk_sit_hash_update(const NMPlatformLnkSit *obj, NMHashState *h);
 void nm_platform_lnk_tun_hash_update(const NMPlatformLnkTun *obj, NMHashState *h);
 void nm_platform_lnk_vlan_hash_update(const NMPlatformLnkVlan *obj, NMHashState *h);
@@ -2621,6 +2655,10 @@ gboolean nm_platform_ethtool_set_channels(NMPlatform                   *self,
                                           int                           ifindex,
                                           const NMEthtoolChannelsState *channels);
 
+gboolean nm_platform_ethtool_get_fec_mode(NMPlatform *self, int ifindex, uint32_t *fec_mode);
+
+gboolean nm_platform_ethtool_set_fec_mode(NMPlatform *self, int ifindex, uint32_t fec_mode);
+
 gboolean
 nm_platform_ethtool_get_link_pause(NMPlatform *self, int ifindex, NMEthtoolPauseState *pause);
 
@@ -2657,7 +2695,7 @@ void     nm_platform_ip6_dadfailed_set(NMPlatform            *self,
 static inline NMPlatformIP4Address *
 nm_platform_ip4_address_init_loopback_addr1(NMPlatformIP4Address *a)
 {
-    *a = ((NMPlatformIP4Address){
+    *a = ((NMPlatformIP4Address) {
         .address      = NM_IPV4LO_ADDR1,
         .peer_address = NM_IPV4LO_ADDR1,
         .ifindex      = NM_LOOPBACK_IFINDEX,
@@ -2669,7 +2707,7 @@ nm_platform_ip4_address_init_loopback_addr1(NMPlatformIP4Address *a)
 static inline NMPlatformIP6Address *
 nm_platform_ip6_address_init_loopback(NMPlatformIP6Address *a)
 {
-    *a = ((NMPlatformIP6Address){
+    *a = ((NMPlatformIP6Address) {
         .address = IN6ADDR_LOOPBACK_INIT,
         .ifindex = NM_LOOPBACK_IFINDEX,
         .plen    = 128,
diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h
index 3784a78e..f6656296 100644
--- a/src/libnm-platform/nmp-base.h
+++ b/src/libnm-platform/nmp-base.h
@@ -174,6 +174,7 @@ typedef enum _nm_packed {
     NMP_OBJECT_TYPE_LNK_IP6GRE,
     NMP_OBJECT_TYPE_LNK_IP6GRETAP,
     NMP_OBJECT_TYPE_LNK_IPIP,
+    NMP_OBJECT_TYPE_LNK_IPVLAN,
     NMP_OBJECT_TYPE_LNK_MACSEC,
     NMP_OBJECT_TYPE_LNK_MACVLAN,
     NMP_OBJECT_TYPE_LNK_MACVTAP,
diff --git a/src/libnm-platform/nmp-global-tracker.c b/src/libnm-platform/nmp-global-tracker.c
index b06e9fe5..8caa70ef 100644
--- a/src/libnm-platform/nmp-global-tracker.c
+++ b/src/libnm-platform/nmp-global-tracker.c
@@ -422,7 +422,7 @@ nmp_global_tracker_track(NMPGlobalTracker *self,
 
     if (!track_data) {
         track_data  = g_slice_new(TrackData);
-        *track_data = (TrackData){
+        *track_data = (TrackData) {
             .obj      = nm_dedup_multi_index_obj_intern(nm_platform_get_multi_idx(self->platform),
                                                    p_obj_stack),
             .user_tag = user_tag,
@@ -435,7 +435,7 @@ nmp_global_tracker_track(NMPGlobalTracker *self,
         obj_data = g_hash_table_lookup(self->by_obj, &track_data->obj);
         if (!obj_data) {
             obj_data  = g_slice_new(TrackObjData);
-            *obj_data = (TrackObjData){
+            *obj_data = (TrackObjData) {
                 .obj          = nmp_object_ref(track_data->obj),
                 .obj_lst_head = C_LIST_INIT(obj_data->obj_lst_head),
                 .config_state = CONFIG_STATE_NONE,
@@ -448,7 +448,7 @@ nmp_global_tracker_track(NMPGlobalTracker *self,
         user_tag_data = g_hash_table_lookup(self->by_user_tag, &track_data->user_tag);
         if (!user_tag_data) {
             user_tag_data  = g_slice_new(TrackUserTagData);
-            *user_tag_data = (TrackUserTagData){
+            *user_tag_data = (TrackUserTagData) {
                 .user_tag          = user_tag,
                 .user_tag_lst_head = C_LIST_INIT(user_tag_data->user_tag_lst_head),
             };
@@ -651,7 +651,7 @@ nmp_global_tracker_mptcp_addr_init_for_ifindex(NMPlatformMptcpAddr *addr, int if
     nm_assert(addr);
     nm_assert(ifindex > 0);
 
-    *addr = (NMPlatformMptcpAddr){
+    *addr = (NMPlatformMptcpAddr) {
         .ifindex     = ifindex,
         .addr_family = AF_UNSPEC,
     };
@@ -776,7 +776,7 @@ nmp_global_tracker_sync_mptcp_addrs(NMPGlobalTracker *self, gboolean reapply)
             entries = g_array_new(FALSE, FALSE, sizeof(MptcpSyncData));
 
         g_array_append_val(entries,
-                           ((const MptcpSyncData){
+                           ((const MptcpSyncData) {
                                .obj_data = obj_data,
                                .td_best  = td_best,
                            }));
@@ -1158,7 +1158,7 @@ nmp_global_tracker_track_rule_default(NMPGlobalTracker *self,
     if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) {
         nmp_global_tracker_track_local_rule(self, addr_family, track_priority, user_tag, NULL);
         nmp_global_tracker_track_rule(self,
-                                      &((NMPlatformRoutingRule){
+                                      &((NMPlatformRoutingRule) {
                                           .addr_family = AF_INET,
                                           .priority    = 32766,
                                           .table       = RT_TABLE_MAIN,
@@ -1169,7 +1169,7 @@ nmp_global_tracker_track_rule_default(NMPGlobalTracker *self,
                                       user_tag,
                                       NULL);
         nmp_global_tracker_track_rule(self,
-                                      &((NMPlatformRoutingRule){
+                                      &((NMPlatformRoutingRule) {
                                           .addr_family = AF_INET,
                                           .priority    = 32767,
                                           .table       = RT_TABLE_DEFAULT,
@@ -1183,7 +1183,7 @@ nmp_global_tracker_track_rule_default(NMPGlobalTracker *self,
     if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) {
         nmp_global_tracker_track_local_rule(self, addr_family, track_priority, user_tag, NULL);
         nmp_global_tracker_track_rule(self,
-                                      &((NMPlatformRoutingRule){
+                                      &((NMPlatformRoutingRule) {
                                           .addr_family = AF_INET6,
                                           .priority    = 32766,
                                           .table       = RT_TABLE_MAIN,
@@ -1209,7 +1209,7 @@ nmp_global_tracker_track_local_rule(NMPGlobalTracker *self,
 
     if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) {
         nmp_global_tracker_track_rule(self,
-                                      &((NMPlatformRoutingRule){
+                                      &((NMPlatformRoutingRule) {
                                           .addr_family = AF_INET,
                                           .priority    = 0,
                                           .table       = RT_TABLE_LOCAL,
@@ -1222,7 +1222,7 @@ nmp_global_tracker_track_local_rule(NMPGlobalTracker *self,
     }
     if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) {
         nmp_global_tracker_track_rule(self,
-                                      &((NMPlatformRoutingRule){
+                                      &((NMPlatformRoutingRule) {
                                           .addr_family = AF_INET6,
                                           .priority    = 0,
                                           .table       = RT_TABLE_LOCAL,
@@ -1247,7 +1247,7 @@ nmp_global_tracker_new(NMPlatform *platform)
     G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(TrackUserTagData, user_tag) == 0);
 
     self  = g_slice_new(NMPGlobalTracker);
-    *self = (NMPGlobalTracker){
+    *self = (NMPGlobalTracker) {
         .ref_count = 1,
         .platform  = g_object_ref(platform),
         .by_data =
diff --git a/src/libnm-platform/nmp-netns.c b/src/libnm-platform/nmp-netns.c
index c18f67ab..a0de8366 100644
--- a/src/libnm-platform/nmp-netns.c
+++ b/src/libnm-platform/nmp-netns.c
@@ -265,7 +265,7 @@ _stack_push(GArray *netns_stack, NMPNetns *netns, int ns_types)
     nm_assert(!NM_FLAGS_ANY(ns_types, ~_CLONE_NS_ALL));
 
     info  = nm_g_array_append_new(netns_stack, NetnsInfo);
-    *info = (NetnsInfo){
+    *info = (NetnsInfo) {
         .netns    = g_object_ref(netns),
         .ns_types = ns_types,
         .count    = 1,
diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c
index 1fdaa275..f41cc95a 100644
--- a/src/libnm-platform/nmp-object.c
+++ b/src/libnm-platform/nmp-object.c
@@ -3565,6 +3565,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
             .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ipip_hash_update,
             .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_ipip_cmp,
         },
+    [NMP_OBJECT_TYPE_LNK_IPVLAN - 1] =
+        {
+            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
+            .obj_type              = NMP_OBJECT_TYPE_LNK_IPVLAN,
+            .sizeof_data           = sizeof(NMPObjectLnkIpvlan),
+            .sizeof_public         = sizeof(NMPlatformLnkIpvlan),
+            .obj_type_name         = "ipvlan",
+            .lnk_link_type         = NM_LINK_TYPE_IPVLAN,
+            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_ipvlan_to_string,
+            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_ipvlan_hash_update,
+            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_ipvlan_cmp,
+        },
     [NMP_OBJECT_TYPE_LNK_MACSEC - 1] =
         {
             .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h
index cd1793d4..b526fc3b 100644
--- a/src/libnm-platform/nmp-object.h
+++ b/src/libnm-platform/nmp-object.h
@@ -271,6 +271,10 @@ typedef struct {
 } NMPObjectLnkIpIp;
 
 typedef struct {
+    NMPlatformLnkIpvlan _public;
+} NMPObjectLnkIpvlan;
+
+typedef struct {
     NMPlatformLnkMacsec _public;
 } NMPObjectLnkMacsec;
 
@@ -394,6 +398,9 @@ struct _NMPObject {
         NMPlatformLnkIp6Tnl lnk_ip6tnl;
         NMPObjectLnkIp6Tnl  _lnk_ip6tnl;
 
+        NMPlatformLnkIpvlan lnk_ipvlan;
+        NMPObjectLnkIpvlan  _lnk_ipvlan;
+
         NMPlatformLnkMacsec lnk_macsec;
         NMPObjectLnkMacsec  _lnk_macsec;
 
@@ -544,6 +551,7 @@ _NMP_OBJECT_TYPE_IS_OBJ_WITH_IFINDEX(NMPObjectType obj_type)
     case NMP_OBJECT_TYPE_LNK_IP6GRE:
     case NMP_OBJECT_TYPE_LNK_IP6GRETAP:
     case NMP_OBJECT_TYPE_LNK_IPIP:
+    case NMP_OBJECT_TYPE_LNK_IPVLAN:
     case NMP_OBJECT_TYPE_LNK_MACSEC:
     case NMP_OBJECT_TYPE_LNK_MACVLAN:
     case NMP_OBJECT_TYPE_LNK_MACVTAP:
diff --git a/src/libnm-platform/nmp-plobj.h b/src/libnm-platform/nmp-plobj.h
index ad18573f..7760c02c 100644
--- a/src/libnm-platform/nmp-plobj.h
+++ b/src/libnm-platform/nmp-plobj.h
@@ -156,9 +156,9 @@ typedef enum {
                  NMPlatformIP4Address,       \
                  NMPlatformIP6Address)
 
-#define NM_PLATFORM_IP4_ADDRESS_INIT(...) (&((const NMPlatformIP4Address){__VA_ARGS__}))
+#define NM_PLATFORM_IP4_ADDRESS_INIT(...) (&((const NMPlatformIP4Address) {__VA_ARGS__}))
 
-#define NM_PLATFORM_IP6_ADDRESS_INIT(...) (&((const NMPlatformIP6Address){__VA_ARGS__}))
+#define NM_PLATFORM_IP6_ADDRESS_INIT(...) (&((const NMPlatformIP6Address) {__VA_ARGS__}))
 
 /*****************************************************************************/
 
@@ -195,6 +195,15 @@ nm_platform_ip4_address_cmp_full(const NMPlatformIP4Address *a, const NMPlatform
     return nm_platform_ip4_address_cmp(a, b, NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL);
 }
 
+static inline gboolean
+nm_platform_ip4_address_is_link_local(const NMPlatformIP4Address *a)
+{
+    nm_assert(a);
+
+    return nm_ip4_addr_is_link_local(a->address) && a->plen == NM_IPV4LL_PREFIXLEN
+           && a->address == a->peer_address;
+}
+
 void nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h);
 
 int nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a,
diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c
index 37707875..22148f8c 100644
--- a/src/libnm-platform/tests/test-nm-platform.c
+++ b/src/libnm-platform/tests/test-nm-platform.c
@@ -198,12 +198,12 @@ test_nmp_utils_bridge_vlans_normalize(void)
     guint                vlans_len;
 
     /* Single one is unmodified */
-    vlans[0] = (NMPlatformBridgeVlan){
+    vlans[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 10,
         .untagged  = TRUE,
     };
-    expect[0] = (NMPlatformBridgeVlan){
+    expect[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 10,
         .untagged  = TRUE,
@@ -214,61 +214,61 @@ test_nmp_utils_bridge_vlans_normalize(void)
     g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len));
 
     /* Not merged if flags are different */
-    vlans[0] = (NMPlatformBridgeVlan){
+    vlans[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 10,
         .untagged  = TRUE,
     };
-    vlans[1] = (NMPlatformBridgeVlan){
+    vlans[1] = (NMPlatformBridgeVlan) {
         .vid_start = 11,
         .vid_end   = 11,
         .pvid      = TRUE,
     };
-    vlans[2] = (NMPlatformBridgeVlan){
+    vlans[2] = (NMPlatformBridgeVlan) {
         .vid_start = 20,
         .vid_end   = 25,
     };
-    vlans[3] = (NMPlatformBridgeVlan){
+    vlans[3] = (NMPlatformBridgeVlan) {
         .vid_start = 26,
         .vid_end   = 30,
         .untagged  = TRUE,
     };
-    vlans[4] = (NMPlatformBridgeVlan){
+    vlans[4] = (NMPlatformBridgeVlan) {
         .vid_start = 40,
         .vid_end   = 40,
         .untagged  = TRUE,
     };
-    vlans[5] = (NMPlatformBridgeVlan){
+    vlans[5] = (NMPlatformBridgeVlan) {
         .vid_start = 40,
         .vid_end   = 40,
         .untagged  = TRUE,
         .pvid      = TRUE,
     };
-    expect[0] = (NMPlatformBridgeVlan){
+    expect[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 10,
         .untagged  = TRUE,
     };
-    expect[1] = (NMPlatformBridgeVlan){
+    expect[1] = (NMPlatformBridgeVlan) {
         .vid_start = 11,
         .vid_end   = 11,
         .pvid      = TRUE,
     };
-    expect[2] = (NMPlatformBridgeVlan){
+    expect[2] = (NMPlatformBridgeVlan) {
         .vid_start = 20,
         .vid_end   = 25,
     };
-    expect[3] = (NMPlatformBridgeVlan){
+    expect[3] = (NMPlatformBridgeVlan) {
         .vid_start = 26,
         .vid_end   = 30,
         .untagged  = TRUE,
     };
-    expect[4] = (NMPlatformBridgeVlan){
+    expect[4] = (NMPlatformBridgeVlan) {
         .vid_start = 40,
         .vid_end   = 40,
         .untagged  = TRUE,
     };
-    expect[5] = (NMPlatformBridgeVlan){
+    expect[5] = (NMPlatformBridgeVlan) {
         .vid_start = 40,
         .vid_end   = 40,
         .untagged  = TRUE,
@@ -280,22 +280,22 @@ test_nmp_utils_bridge_vlans_normalize(void)
     g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len));
 
     /* Overlapping and contiguous ranges are merged */
-    vlans[0] = (NMPlatformBridgeVlan){
+    vlans[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 10,
         .untagged  = TRUE,
     };
-    vlans[1] = (NMPlatformBridgeVlan){
+    vlans[1] = (NMPlatformBridgeVlan) {
         .vid_start = 11,
         .vid_end   = 20,
         .untagged  = TRUE,
     };
-    vlans[2] = (NMPlatformBridgeVlan){
+    vlans[2] = (NMPlatformBridgeVlan) {
         .vid_start = 19,
         .vid_end   = 30,
         .untagged  = TRUE,
     };
-    expect[0] = (NMPlatformBridgeVlan){
+    expect[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 30,
         .untagged  = TRUE,
@@ -305,42 +305,42 @@ test_nmp_utils_bridge_vlans_normalize(void)
     g_assert(vlans_len == 1);
     g_assert(nmp_utils_bridge_normalized_vlans_equal(vlans, vlans_len, expect, vlans_len));
 
-    vlans[0] = (NMPlatformBridgeVlan){
+    vlans[0] = (NMPlatformBridgeVlan) {
         .vid_start = 20,
         .vid_end   = 20,
     };
-    vlans[1] = (NMPlatformBridgeVlan){
+    vlans[1] = (NMPlatformBridgeVlan) {
         .vid_start = 4,
         .vid_end   = 4,
         .pvid      = TRUE,
     };
-    vlans[2] = (NMPlatformBridgeVlan){
+    vlans[2] = (NMPlatformBridgeVlan) {
         .vid_start = 33,
         .vid_end   = 33,
     };
-    vlans[3] = (NMPlatformBridgeVlan){
+    vlans[3] = (NMPlatformBridgeVlan) {
         .vid_start = 100,
         .vid_end   = 100,
         .untagged  = TRUE,
     };
-    vlans[4] = (NMPlatformBridgeVlan){
+    vlans[4] = (NMPlatformBridgeVlan) {
         .vid_start = 34,
         .vid_end   = 40,
     };
-    vlans[5] = (NMPlatformBridgeVlan){
+    vlans[5] = (NMPlatformBridgeVlan) {
         .vid_start = 21,
         .vid_end   = 32,
     };
-    expect[0] = (NMPlatformBridgeVlan){
+    expect[0] = (NMPlatformBridgeVlan) {
         .vid_start = 4,
         .vid_end   = 4,
         .pvid      = TRUE,
     };
-    expect[1] = (NMPlatformBridgeVlan){
+    expect[1] = (NMPlatformBridgeVlan) {
         .vid_start = 20,
         .vid_end   = 40,
     };
-    expect[2] = (NMPlatformBridgeVlan){
+    expect[2] = (NMPlatformBridgeVlan) {
         .vid_start = 100,
         .vid_end   = 100,
         .untagged  = TRUE,
@@ -364,7 +364,7 @@ test_nmp_utils_bridge_normalized_vlans_equal(void)
     g_assert(nmp_utils_bridge_normalized_vlans_equal(NULL, 0, b, 0));
 
     /* One empty, other not */
-    a[0] = (NMPlatformBridgeVlan){
+    a[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 10,
         .untagged  = TRUE,
@@ -373,22 +373,22 @@ test_nmp_utils_bridge_normalized_vlans_equal(void)
     g_assert(!nmp_utils_bridge_normalized_vlans_equal(NULL, 0, a, 1));
 
     /* Equal range + VLAN */
-    a[0] = (NMPlatformBridgeVlan){
+    a[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 10,
         .untagged  = TRUE,
     };
-    a[1] = (NMPlatformBridgeVlan){
+    a[1] = (NMPlatformBridgeVlan) {
         .vid_start = 11,
         .vid_end   = 11,
         .pvid      = TRUE,
     };
-    b[0] = (NMPlatformBridgeVlan){
+    b[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 10,
         .untagged  = TRUE,
     };
-    b[1] = (NMPlatformBridgeVlan){
+    b[1] = (NMPlatformBridgeVlan) {
         .vid_start = 11,
         .vid_end   = 11,
         .pvid      = TRUE,
@@ -402,12 +402,12 @@ test_nmp_utils_bridge_normalized_vlans_equal(void)
     g_assert(!nmp_utils_bridge_normalized_vlans_equal(b, 2, a, 2));
 
     /* Different ranges */
-    a[0] = (NMPlatformBridgeVlan){
+    a[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 30,
         .untagged  = TRUE,
     };
-    b[0] = (NMPlatformBridgeVlan){
+    b[0] = (NMPlatformBridgeVlan) {
         .vid_start = 1,
         .vid_end   = 29,
         .untagged  = TRUE,
diff --git a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
index 3c00898a..020c054b 100644
--- a/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
+++ b/src/libnm-platform/wifi/nm-wifi-utils-nl80211.c
@@ -689,7 +689,7 @@ nl80211_wiphy_info_handler(const struct nl_msg *msg, void *arg)
                 }
 
                 f  = &info->freqs[info->num_freqs];
-                *f = (Nl80211Freq){
+                *f = (Nl80211Freq) {
                     .freq     = nla_get_u32(tb_freq[NL80211_FREQUENCY_ATTR_FREQ]),
                     .disabled = !!tb_freq[NL80211_FREQUENCY_ATTR_DISABLED],
                     .no_ir    = !!tb_freq[NL80211_FREQUENCY_ATTR_NO_IR],