summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/devices/nm-device-hsr.c36
-rw-r--r--src/core/devices/nm-device-utils.c6
-rw-r--r--src/core/devices/nm-device.c265
-rw-r--r--src/core/devices/nm-device.h23
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c32
-rw-r--r--src/core/nm-manager.c110
-rw-r--r--src/core/nm-manager.h4
-rw-r--r--src/core/nm-policy.c97
-rw-r--r--src/libnm-client-impl/libnm.ver7
-rw-r--r--src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in8
-rw-r--r--src/libnm-core-impl/nm-setting-hsr.c90
-rw-r--r--src/libnm-core-public/nm-dbus-interface.h21
-rw-r--r--src/libnm-core-public/nm-setting-hsr.h30
-rw-r--r--src/libnm-core-public/nm-version-macros.h.in1
-rw-r--r--src/libnm-core-public/nm-version.h6
-rw-r--r--src/libnm-platform/nm-linux-platform.c12
-rw-r--r--src/libnm-platform/nm-platform.c17
-rw-r--r--src/libnm-platform/nm-platform.h2
-rw-r--r--src/libnmc-base/nm-client-utils.c3
-rw-r--r--src/libnmc-setting/nm-meta-setting-desc.c6
-rw-r--r--src/libnmc-setting/nm-meta-setting-desc.h3
-rw-r--r--src/libnmc-setting/settings-docs.h.in2
-rw-r--r--src/nmcli/gen-metadata-nm-settings-nmcli.xml.in7
-rw-r--r--src/nmcli/general.c6
-rw-r--r--src/nmcli/nmcli.c1
25 files changed, 550 insertions, 245 deletions
diff --git a/src/core/devices/nm-device-hsr.c b/src/core/devices/nm-device-hsr.c
index 59454ee3..e3368a5b 100644
--- a/src/core/devices/nm-device-hsr.c
+++ b/src/core/devices/nm-device-hsr.c
@@ -116,29 +116,51 @@ create_and_realize(NMDevice              *device,
                    const NMPlatformLink **out_plink,
                    GError               **error)
 {
-    const char      *iface = nm_device_get_iface(device);
-    NMSettingHsr    *s_hsr;
-    NMPlatformLnkHsr lnk = {};
-    int              r;
+    const char        *iface   = nm_device_get_iface(device);
+    nm_auto_free char *err_msg = NULL;
+    NMSettingHsr      *s_hsr;
+    NMPlatformLnkHsr   lnk = {};
+    int                r   = 0;
 
     s_hsr = _nm_connection_get_setting(connection, NM_TYPE_SETTING_HSR);
+
     nm_assert(s_hsr);
 
     if (nm_setting_hsr_get_port1(s_hsr) != NULL)
         lnk.port1 = nm_platform_link_get_ifindex(NM_PLATFORM_GET, nm_setting_hsr_get_port1(s_hsr));
     if (nm_setting_hsr_get_port2(s_hsr) != NULL)
         lnk.port2 = nm_platform_link_get_ifindex(NM_PLATFORM_GET, nm_setting_hsr_get_port2(s_hsr));
-    lnk.multicast_spec = nm_setting_hsr_get_multicast_spec(s_hsr);
-    lnk.prp            = nm_setting_hsr_get_prp(s_hsr);
+    if (nm_setting_hsr_get_interlink(s_hsr) != NULL) {
+        const char *ifname  = nm_setting_hsr_get_interlink(s_hsr);
+        int         ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, ifname);
+
+        if (ifindex <= 0) {
+            err_msg = g_strdup_printf("interlink port '%s' does not exist", ifname);
+            goto out;
+        }
+
+        lnk.interlink = ifindex;
+    }
+
+    lnk.multicast_spec   = nm_setting_hsr_get_multicast_spec(s_hsr);
+    lnk.prp              = nm_setting_hsr_get_prp(s_hsr);
+    lnk.protocol_version = nm_setting_hsr_get_protocol_version(s_hsr);
+
     r = nm_platform_link_hsr_add(nm_device_get_platform(device), iface, &lnk, out_plink);
+
     if (r < 0) {
+        err_msg = g_strdup(nm_strerror(r) ?: "unknown");
+    }
+
+out:
+    if (err_msg) {
         g_set_error(error,
                     NM_DEVICE_ERROR,
                     NM_DEVICE_ERROR_CREATION_FAILED,
                     "Failed to create HSR interface '%s' for '%s': %s",
                     iface,
                     nm_connection_get_id(connection),
-                    nm_strerror(r));
+                    err_msg);
         return FALSE;
     }
 
diff --git a/src/core/devices/nm-device-utils.c b/src/core/devices/nm-device-utils.c
index 9fd7ac9d..a78499fb 100644
--- a/src/core/devices/nm-device-utils.c
+++ b/src/core/devices/nm-device-utils.c
@@ -135,13 +135,15 @@ NM_UTILS_LOOKUP_STR_DEFINE(
     NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_LINK_NOT_INIT,
                              "unmanaged-link-not-init"),
     NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING, "unmanaged-quitting"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING, "unmanaged-sleeping"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_MANAGER_DISABLED,
+                             "unmanaged-nm-disabled"),
     NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_CONF, "unmanaged-user-conf"),
     NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT,
                              "unmanaged-user-explicit"),
     NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_SETTINGS,
                              "unmanaged-user-settings"),
-    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV, "unmanaged-user-udev"), );
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV, "unmanaged-user-udev"),
+    NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NETWORKING_OFF, "networking-off"), );
 
 NM_UTILS_LOOKUP_STR_DEFINE(nm_device_mtu_source_to_string,
                            NMDeviceMtuSource,
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 79b1076d..6bba982a 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -692,6 +692,8 @@ typedef struct _NMDevicePrivate {
 
     IPDevStateData ipdev_data_unspec;
 
+    gulong sharing_ipv4_changed_id;
+
     struct {
         /* If we set the addrgenmode6, this records the previously set value. */
         guint8 previous_mode_val;
@@ -779,7 +781,6 @@ typedef struct _NMDevicePrivate {
     char     *prop_ip_iface; /* IP interface D-Bus property */
     GList    *ping_operations;
     GSource  *ping_timeout;
-    bool      refresh_forwarding_done : 1;
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -867,6 +868,8 @@ static void _dev_ipshared4_spawn_dnsmasq(NMDevice *self);
 
 static void _dev_ipshared6_start(NMDevice *self);
 
+static void _dev_ipforwarding4_start(NMDevice *self, int addr_family);
+
 static void
 _cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gboolean preserve_dhcp);
 
@@ -2131,8 +2134,8 @@ _prop_get_ipvx_dhcp_send_hostname(NMDevice *self, int addr_family)
     return send_hostname_v2;
 }
 
-NMSettingIPConfigForwarding
-nm_device_get_ipv4_forwarding(NMDevice *self)
+static NMSettingIPConfigForwarding
+_prop_get_ipv4_forwarding(NMDevice *self)
 {
     NMSettingIPConfig          *s_ip;
     NMSettingIPConfigForwarding forwarding;
@@ -3778,7 +3781,7 @@ nm_device_assume_state_reset(NMDevice *self)
 
 /*****************************************************************************/
 
-char *
+static char *
 nm_device_sysctl_ip_conf_get(NMDevice *self, int addr_family, const char *property)
 {
     const char *ifname;
@@ -6645,7 +6648,7 @@ concheck_update_state(NMDevice           *self,
     }
 }
 
-const char *
+static const char *
 nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family)
 {
     NMDeviceClass *klass;
@@ -8639,6 +8642,8 @@ nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error)
 
     g_object_thaw_notify(G_OBJECT(self));
 
+    nm_device_managed_type_set(self, NM_DEVICE_MANAGED_TYPE_REMOVED);
+
     nm_device_set_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT, TRUE);
 
     nm_device_set_unmanaged_flags(self,
@@ -10394,6 +10399,43 @@ sriov_params_cb(GError *error, gpointer user_data)
     nm_device_activate_schedule_stage1_device_prepare(self, FALSE);
 }
 
+static gboolean
+sriov_gen_platform_vfs(NMDevice       *self,
+                       NMSettingSriov *s_sriov,
+                       NMPlatformVF ***plat_vfs_out,
+                       GError        **error)
+{
+    nm_auto_freev NMPlatformVF **plat_vfs = NULL;
+    guint                        num;
+
+    nm_assert(s_sriov);
+    nm_assert(plat_vfs_out && !*plat_vfs_out);
+
+    num      = nm_setting_sriov_get_num_vfs(s_sriov);
+    plat_vfs = g_new0(NMPlatformVF *, num + 1);
+
+    for (int i = 0; i < num; i++) {
+        NMSriovVF            *vf    = nm_setting_sriov_get_vf(s_sriov, i);
+        gs_free_error GError *local = NULL;
+
+        plat_vfs[i] = sriov_vf_config_to_platform(self, vf, &local);
+
+        if (!plat_vfs[i]) {
+            g_set_error(error,
+                        local->domain,
+                        local->code,
+                        "VF '%s' is invalid: %s",
+                        nm_utils_sriov_vf_to_str(vf, FALSE, NULL),
+                        local->message);
+            return FALSE;
+        }
+    }
+
+    *plat_vfs_out = g_steal_pointer(&plat_vfs);
+
+    return TRUE;
+}
+
 /*
  * activate_stage1_device_prepare
  *
@@ -10440,10 +10482,7 @@ activate_stage1_device_prepare(NMDevice *self)
         if (s_sriov && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)) {
             nm_auto_freev NMPlatformVF **plat_vfs = NULL;
             gs_free_error GError        *error    = NULL;
-            NMSriovVF                   *vf;
             NMTernary                    autoprobe;
-            guint                        num;
-            guint                        i;
 
             autoprobe = nm_setting_sriov_get_autoprobe_drivers(s_sriov);
             if (autoprobe == NM_TERNARY_DEFAULT) {
@@ -10456,21 +10495,12 @@ activate_stage1_device_prepare(NMDevice *self)
                     NM_OPTION_BOOL_TRUE);
             }
 
-            num      = nm_setting_sriov_get_num_vfs(s_sriov);
-            plat_vfs = g_new0(NMPlatformVF *, num + 1);
-            for (i = 0; i < num; i++) {
-                vf          = nm_setting_sriov_get_vf(s_sriov, i);
-                plat_vfs[i] = sriov_vf_config_to_platform(self, vf, &error);
-                if (!plat_vfs[i]) {
-                    _LOGE(LOGD_DEVICE,
-                          "failed to apply SR-IOV VF '%s': %s",
-                          nm_utils_sriov_vf_to_str(vf, FALSE, NULL),
-                          error->message);
-                    nm_device_state_changed(self,
-                                            NM_DEVICE_STATE_FAILED,
-                                            NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
-                    return;
-                }
+            if (!sriov_gen_platform_vfs(self, s_sriov, &plat_vfs, &error)) {
+                _LOGE(LOGD_DEVICE, "cannot parse the VF list: %s", error->message);
+                nm_device_state_changed(self,
+                                        NM_DEVICE_STATE_FAILED,
+                                        NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
+                return;
             }
 
             /* When changing the number of VFs the kernel can block
@@ -13139,17 +13169,12 @@ activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family)
         goto out_devip;
 
     if (IS_IPv4) {
-        NMSettingIPConfigForwarding ipv4_forwarding = nm_device_get_ipv4_forwarding(self);
-
-        if (NM_IN_SET(ipv4_forwarding,
-                      NM_SETTING_IP_CONFIG_FORWARDING_NO,
-                      NM_SETTING_IP_CONFIG_FORWARDING_YES)) {
-            nm_device_sysctl_ip_conf_set(self, AF_INET, "forwarding", ipv4_forwarding ? "1" : "0");
-        }
         priv->ipll_data_4.v4.mode = _prop_get_ipv4_link_local(self);
         if (priv->ipll_data_4.v4.mode == NM_SETTING_IP4_LL_ENABLED)
             _dev_ipll4_start(self);
 
+        _dev_ipforwarding4_start(self, addr_family);
+
         if (nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
             _dev_ipdhcpx_start(self, AF_INET);
         else if (nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
@@ -13469,15 +13494,21 @@ nm_device_activate_schedule_stage3_ip_config(NMDevice *self, gboolean do_sync)
 static void
 _dev_ipsharedx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
 {
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate *priv      = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4   = NM_IS_IPv4(addr_family);
+    NMDeviceIPState  old_state = priv->ipshared_data_x[IS_IPv4].state;
 
-    if (priv->ipshared_data_x[IS_IPv4].state != state) {
+    if (old_state != state) {
         _LOGD_ipshared(addr_family,
                        "set state %s (was %s)",
                        nm_device_ip_state_to_string(state),
-                       nm_device_ip_state_to_string(priv->ipshared_data_x[IS_IPv4].state));
+                       nm_device_ip_state_to_string(old_state));
         priv->ipshared_data_x[IS_IPv4].state = state;
+
+        if (old_state == NM_DEVICE_IP_STATE_READY || state == NM_DEVICE_IP_STATE_READY)
+            nm_manager_update_shared_connection(NM_MANAGER_GET,
+                                                addr_family,
+                                                state == NM_DEVICE_IP_STATE_READY);
     }
 }
 
@@ -13772,6 +13803,106 @@ _dev_ipshared6_start(NMDevice *self)
 
 /*****************************************************************************/
 
+/**
+ * Set the device's forwarding to the specified value. If %NM_TERNARY_DEFAULT is specified,
+ * it's set to the kernel's default, otherwise it's set to the specific value.
+ */
+static void
+_dev_ipforwarding4_set(NMDevice *self, NMTernary val)
+{
+    gs_free const char *default_forwarding = NULL;
+    gs_free const char *current_forwarding = NULL;
+    const char         *val_str;
+
+    if (val != NM_TERNARY_DEFAULT) {
+        val_str = val ? "1" : "0";
+    } else {
+        default_forwarding = nm_platform_sysctl_get(
+            nm_device_get_platform(self),
+            NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/conf/default/forwarding"));
+
+        if (!default_forwarding) {
+            _LOGW(LOGD_DEVICE,
+                  "error setting IPv4 forwarding: can't read default forwarding value: %s",
+                  nm_strerror_native(errno));
+            return; /* Non fatal */
+        }
+
+        val_str = default_forwarding;
+    }
+
+    current_forwarding = nm_device_sysctl_ip_conf_get(self, AF_INET, "forwarding");
+    if (nm_streq0(current_forwarding, val_str))
+        return;
+
+    if (!nm_device_sysctl_ip_conf_set(self, AF_INET, "forwarding", val_str))
+        _LOGW(LOGD_DEVICE,
+              "error setting IPv4 forwarding to '%s': %s",
+              val_str,
+              nm_strerror_native(errno));
+}
+
+static void
+_dev_ipforwarding4_auto_cb(NMManager *manager, gboolean sharing_ipv4, gpointer data)
+{
+    NMDevice *self = NM_DEVICE(data);
+
+    _dev_ipforwarding4_set(self, sharing_ipv4 ? NM_TERNARY_TRUE : NM_TERNARY_DEFAULT);
+}
+
+static void
+_dev_ipforwarding4_start(NMDevice *self, int addr_family)
+{
+    NMDevicePrivate            *priv            = NM_DEVICE_GET_PRIVATE(self);
+    NMSettingIPConfigForwarding ipv4_forwarding = _prop_get_ipv4_forwarding(self);
+    NMTernary                   new_forwarding  = NM_TERNARY_DEFAULT;
+
+    /* IPv6 per-interface forwarding not supported yet */
+    if (addr_family != AF_INET)
+        return;
+
+    if (nm_streq(priv->ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
+        new_forwarding = NM_TERNARY_TRUE;
+    } else if (ipv4_forwarding == NM_SETTING_IP_CONFIG_FORWARDING_YES) {
+        new_forwarding = NM_TERNARY_TRUE;
+    } else if (ipv4_forwarding == NM_SETTING_IP_CONFIG_FORWARDING_NO) {
+        new_forwarding = NM_TERNARY_FALSE;
+    } else if (ipv4_forwarding == NM_SETTING_IP_CONFIG_FORWARDING_AUTO) {
+        if (nm_manager_get_sharing_ipv4(NM_MANAGER_GET))
+            new_forwarding = NM_TERNARY_TRUE;
+        else
+            new_forwarding = NM_TERNARY_DEFAULT;
+
+        if (!priv->sharing_ipv4_changed_id)
+            priv->sharing_ipv4_changed_id = g_signal_connect(NM_MANAGER_GET,
+                                                             NM_MANAGER_SHARING_IPV4_CHANGED,
+                                                             G_CALLBACK(_dev_ipforwarding4_auto_cb),
+                                                             self);
+    } else {
+        nm_assert_not_reached();
+    }
+
+    _dev_ipforwarding4_set(self, new_forwarding);
+}
+
+static void
+_dev_ipforwarding_cleanup(NMDevice *self, int addr_family, CleanupType cleanup_type)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    if (!NM_IS_IPv4(addr_family))
+        return;
+
+    nm_clear_g_signal_handler(NM_MANAGER_GET, &priv->sharing_ipv4_changed_id);
+
+    if (NM_IN_SET(cleanup_type, CLEANUP_TYPE_DECONFIGURE, CLEANUP_TYPE_KEEP_REAPPLY)) {
+        /* Deconfigure by restoring kernel's default */
+        _dev_ipforwarding4_set(self, NM_TERNARY_DEFAULT);
+    }
+}
+
+/*****************************************************************************/
+
 static void
 act_request_set(NMDevice *self, NMActRequest *act_request)
 {
@@ -13884,6 +14015,8 @@ _cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gbool
     NMDevicePrivate *priv         = NM_DEVICE_GET_PRIVATE(self);
     gboolean         keep_reapply = (cleanup_type == CLEANUP_TYPE_KEEP_REAPPLY);
 
+    _dev_ipforwarding_cleanup(self, addr_family, cleanup_type);
+
     _dev_ipsharedx_cleanup(self, addr_family);
 
     _dev_ipdev_cleanup(self, AF_UNSPEC);
@@ -14066,7 +14199,8 @@ can_reapply_change(NMDevice   *self,
         return nm_device_hash_check_invalid_keys(diffs,
                                                  NM_SETTING_SRIOV_SETTING_NAME,
                                                  error,
-                                                 NM_SETTING_SRIOV_PRESERVE_ON_DOWN);
+                                                 NM_SETTING_SRIOV_PRESERVE_ON_DOWN,
+                                                 NM_SETTING_SRIOV_VFS);
     }
 
 out_fail:
@@ -14244,9 +14378,35 @@ check_and_reapply_connection(NMDevice            *self,
 
     nm_device_link_properties_set(self, TRUE);
 
-    if (priv->state >= NM_DEVICE_STATE_CONFIG)
+    if (priv->state >= NM_DEVICE_STATE_CONFIG) {
+        GHashTable *sriov_diff;
+
         lldp_setup(self, NM_TERNARY_DEFAULT);
 
+        sriov_diff = nm_g_hash_table_lookup(diffs, NM_SETTING_SRIOV_SETTING_NAME);
+
+        if (sriov_diff && nm_g_hash_table_lookup(sriov_diff, NM_SETTING_SRIOV_VFS)) {
+            nm_auto_freev NMPlatformVF **plat_vfs = NULL;
+            NMSettingSriov              *s_sriov;
+
+            s_sriov = (NMSettingSriov *) nm_connection_get_setting(applied, NM_TYPE_SETTING_SRIOV);
+
+            if (s_sriov) {
+                gs_free_error GError *local = NULL;
+
+                if (!sriov_gen_platform_vfs(self, s_sriov, &plat_vfs, &local)
+                    || !nm_platform_link_set_sriov_vfs(nm_device_get_platform(self),
+                                                       priv->ifindex,
+                                                       (const NMPlatformVF *const *) plat_vfs)) {
+                    _LOGE(LOGD_DEVICE,
+                          "failed to reapply SRIOV VFs%s%s",
+                          local ? ": " : "",
+                          local ? local->message : "");
+                }
+            }
+        }
+    }
+
     if (priv->state >= NM_DEVICE_STATE_IP_CONFIG) {
         /* Allow reapply of MTU */
         priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
@@ -15613,7 +15773,7 @@ nm_device_get_firmware_missing(NMDevice *self)
 
 NM_UTILS_FLAGS2STR_DEFINE(nm_unmanaged_flags2str,
                           NMUnmanagedFlags,
-                          NM_UTILS_FLAGS2STR(NM_UNMANAGED_SLEEPING, "sleeping"),
+                          NM_UTILS_FLAGS2STR(NM_UNMANAGED_MANAGER_DISABLED, "nm-disabled"),
                           NM_UTILS_FLAGS2STR(NM_UNMANAGED_QUITTING, "quitting"),
                           NM_UTILS_FLAGS2STR(NM_UNMANAGED_PLATFORM_INIT, "platform-init"),
                           NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_EXPLICIT, "user-explicit"),
@@ -15677,8 +15837,8 @@ unmanaged_flags_to_reason(NMUnmanagedFlags flags)
     /* Even if there are multiple flags, we can only return one reason.
      * Return the most important reason.
      */
-    if (NM_FLAGS_HAS(flags, NM_UNMANAGED_SLEEPING))
-        return NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING;
+    if (NM_FLAGS_HAS(flags, NM_UNMANAGED_MANAGER_DISABLED))
+        return NM_DEVICE_STATE_REASON_UNMANAGED_MANAGER_DISABLED;
     if (NM_FLAGS_HAS(flags, NM_UNMANAGED_QUITTING))
         return NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING;
     if (NM_FLAGS_HAS(flags, NM_UNMANAGED_USER_SETTINGS))
@@ -16923,8 +17083,6 @@ _cleanup_generic_post(NMDevice *self, NMDeviceStateReason reason, CleanupType cl
     priv->v4_route_table_all_sync_before = FALSE;
     priv->v6_route_table_all_sync_before = FALSE;
 
-    priv->refresh_forwarding_done = FALSE;
-
     priv->mtu_force_set_done = FALSE;
 
     priv->needs_ip6_subnet = FALSE;
@@ -16970,7 +17128,6 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
     NMDevicePrivate *priv;
     NMDeviceClass   *klass = NM_DEVICE_GET_CLASS(self);
     int              ifindex;
-    gint32           default_forwarding_v4;
 
     g_return_if_fail(NM_IS_DEVICE(self));
 
@@ -16993,17 +17150,6 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
         nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", "0");
     }
 
-    /* Restoring the device's forwarding to the sysctl default is necessary because
-     * `refresh_forwarding()` only updates forwarding on activated devices. */
-    default_forwarding_v4 = nm_platform_sysctl_get_int32(
-        nm_device_get_platform(self),
-        NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/conf/default/forwarding"),
-        0);
-    nm_device_sysctl_ip_conf_set(self,
-                                 AF_INET,
-                                 "forwarding",
-                                 default_forwarding_v4 == 1 ? "1" : "0");
-
     /* Call device type-specific deactivation */
     if (klass->deactivate)
         klass->deactivate(self);
@@ -18947,19 +19093,6 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
     return nm_assert_unreachable_val(NULL);
 }
 
-gboolean
-nm_device_get_refresh_forwarding_done(NMDevice *self)
-{
-    return NM_DEVICE_GET_PRIVATE(self)->refresh_forwarding_done;
-}
-
-void
-nm_device_set_refresh_forwarding_done(NMDevice *self, gboolean is_refresh_forwarding_done)
-{
-    NMDevicePrivate *priv         = NM_DEVICE_GET_PRIVATE(self);
-    priv->refresh_forwarding_done = is_refresh_forwarding_done;
-}
-
 /*****************************************************************************/
 
 static const char *
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index 9a663c2a..8632944a 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -581,7 +581,8 @@ void nm_device_copy_ip6_dns_config(NMDevice *self, NMDevice *from_device);
 /**
  * NMUnmanagedFlags:
  * @NM_UNMANAGED_NONE: placeholder value
- * @NM_UNMANAGED_SLEEPING: %TRUE when unmanaged because NM is sleeping.
+ * @NM_UNMANAGED_MANAGER_DISABLED: %TRUE when unmanaged because NM is disabled.
+ *   Currently, this happens when sleeping or with networking disabled.
  * @NM_UNMANAGED_QUITTING: %TRUE when unmanaged because NM is shutting down.
  * @NM_UNMANAGED_PLATFORM_INIT: %TRUE when unmanaged because platform link not
  *   yet initialized. Unrealized device are also unmanaged for this reason.
@@ -610,11 +611,11 @@ typedef enum {
 
     /* these flags are authoritative. If one of them is set,
      * the device cannot be managed. */
-    NM_UNMANAGED_SLEEPING      = (1LL << 0),
-    NM_UNMANAGED_QUITTING      = (1LL << 1),
-    NM_UNMANAGED_PLATFORM_INIT = (1LL << 2),
-    NM_UNMANAGED_USER_EXPLICIT = (1LL << 3),
-    NM_UNMANAGED_USER_SETTINGS = (1LL << 4),
+    NM_UNMANAGED_MANAGER_DISABLED = (1LL << 0),
+    NM_UNMANAGED_QUITTING         = (1LL << 1),
+    NM_UNMANAGED_PLATFORM_INIT    = (1LL << 2),
+    NM_UNMANAGED_USER_EXPLICIT    = (1LL << 3),
+    NM_UNMANAGED_USER_SETTINGS    = (1LL << 4),
 
     /* These flags can be non-effective and be overwritten
      * by other flags. */
@@ -852,14 +853,4 @@ void nm_routing_rules_sync(NMConnection *applied_connection,
                            NMDevice *self,
                            NMNetns  *netns);
 
-NMSettingIPConfigForwarding nm_device_get_ipv4_forwarding(NMDevice *self);
-
-const char *nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family);
-
-char *nm_device_sysctl_ip_conf_get(NMDevice *self, int addr_family, const char *property);
-
-gboolean nm_device_get_refresh_forwarding_done(NMDevice *self);
-
-void nm_device_set_refresh_forwarding_done(NMDevice *self, gboolean is_refresh_forwarding_done);
-
 #endif /* __NETWORKMANAGER_DEVICE_H__ */
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index 164bfd35..1c9484c4 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -1460,40 +1460,42 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
                 json_array_append_new(new_interfaces, json_pack("[s,s]", "uuid", interface_uuid));
             }
 
-            if (num_nm_interfaces == 0) {
-                /* The port no longer has any NM interface. Don't add it to "new_ports" and set
-                 * ports_changed=TRUE, so that it will be deleted. */
+            if (interfaces_changed && num_nm_interfaces == 0) {
+                /* We are deleting the last nm-interface of this port. Don't add it to "new_ports"
+                 * and set ports_changed=TRUE, so that it will be deleted. */
                 ports_changed = TRUE;
             } else {
+                /* Keep this port: it's still alive, or it's unrelated to the deleted interface */
+                json_array_append_new(new_ports, json_pack("[s,s]", "uuid", port_uuid));
+                if (ovs_port->connection_uuid)
+                    num_nm_ports++;
+
                 if (interfaces_changed) {
-                    /* An interface needs to be deleted from this port */
+                    /* This port is still alive, but an interface needs to be deleted from it */
                     _expect_port_interfaces(params, ovs_port->name, interfaces);
                     _set_port_interfaces(params, ovs_port->name, new_interfaces);
                 }
-                /* The port is still alive */
-                json_array_append_new(new_ports, json_pack("[s,s]", "uuid", port_uuid));
-                if (ovs_port->connection_uuid)
-                    num_nm_ports++;
             }
         }
 
-        if (num_nm_ports == 0) {
-            /* The bridge no longer has any NM port. Don't add it to "new_bridges" and set
-             * bridges_changed=TRUE, so that it will be deleted. */
+        if (ports_changed && num_nm_ports == 0) {
+            /* We are deleting the last nm-port of this bridge. Don't add it to "new_bridges"
+             * and set bridges_changed=TRUE, so that it will be deleted. */
             bridges_changed = TRUE;
         } else {
+            /* Keep this bridge: it's still alive, or it's unrelated to the deleted interface */
+            json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
+
             if (ports_changed) {
-                /* A port needs to be deleted from this bridge */
+                /* This bridge is still alive, but a port needs to be deleted from it */
                 _expect_bridge_ports(params, ovs_bridge->name, ports);
                 _set_bridge_ports(params, ovs_bridge->name, new_ports);
             }
-            /* The bridge is still alive */
-            json_array_append_new(new_bridges, json_pack("[s,s]", "uuid", ovs_bridge->bridge_uuid));
         }
     }
 
     if (bridges_changed) {
-        /* A port needs to be deleted from this bridge */
+        /* A bridge needs to be deleted */
         _expect_ovs_bridges(params, priv->db_uuid, bridges);
         _set_ovs_bridges(params, priv->db_uuid, new_bridges);
     }
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index 9dc7bc01..87dde2c3 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -136,6 +136,7 @@ enum {
     ACTIVE_CONNECTION_REMOVED,
     CONFIGURE_QUIT,
     DEVICE_IFINDEX_CHANGED,
+    SHARING_IPV4_CHANGED,
 
     LAST_SIGNAL
 };
@@ -238,6 +239,8 @@ typedef struct {
 
     guint8 device_state_prune_ratelimit_count;
 
+    guint shared_connections_ip4_count;
+
     bool startup : 1;
     bool devices_inited : 1;
 
@@ -1960,7 +1963,7 @@ find_device_by_iface(NMManager    *self,
 }
 
 static gboolean
-manager_sleeping(NMManager *self)
+manager_is_disabled(NMManager *self)
 {
     NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self);
 
@@ -1973,8 +1976,8 @@ static const char *
 _nm_state_to_string(NMState state)
 {
     switch (state) {
-    case NM_STATE_ASLEEP:
-        return "ASLEEP";
+    case NM_STATE_DISABLED:
+        return "DISABLED";
     case NM_STATE_DISCONNECTED:
         return "DISCONNECTED";
     case NM_STATE_DISCONNECTING:
@@ -2078,15 +2081,18 @@ nm_manager_update_state(NMManager *self)
 {
     NMManagerPrivate *priv;
     NMState           new_state = NM_STATE_DISCONNECTED;
+    const char       *detail    = "";
 
     g_return_if_fail(NM_IS_MANAGER(self));
 
     priv = NM_MANAGER_GET_PRIVATE(self);
 
-    if (manager_sleeping(self))
-        new_state = NM_STATE_ASLEEP;
-    else
+    if (manager_is_disabled(self)) {
+        new_state = NM_STATE_DISABLED;
+        detail    = priv->sleeping ? " (ASLEEP)" : " (NETWORKING OFF)";
+    } else {
         new_state = find_best_device_state(self);
+    }
 
     if (new_state >= NM_STATE_CONNECTED_LOCAL && priv->connectivity_state == NM_CONNECTIVITY_FULL) {
         new_state = NM_STATE_CONNECTED_GLOBAL;
@@ -2097,7 +2103,7 @@ nm_manager_update_state(NMManager *self)
 
     priv->state = new_state;
 
-    _LOGI(LOGD_CORE, "NetworkManager state is now %s", _nm_state_to_string(new_state));
+    _LOGI(LOGD_CORE, "NetworkManager state is now %s%s", _nm_state_to_string(new_state), detail);
 
     _notify(self, PROP_STATE);
     nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self),
@@ -2956,7 +2962,7 @@ _rfkill_update_devices(NMManager *self, NMRfkillType rtype, gboolean enabled)
     _notify(self, _rfkill_type_desc[rtype].prop_id);
 
     /* Don't touch devices if asleep/networking disabled */
-    if (manager_sleeping(self))
+    if (manager_is_disabled(self))
         return;
 
     /* enable/disable wireless devices as required */
@@ -3120,7 +3126,7 @@ _rfkill_update_from_user(NMManager *self, NMRfkillType rtype, gboolean enabled)
     gboolean          old_enabled, new_enabled;
 
     /* Don't touch devices if asleep/networking disabled */
-    if (manager_sleeping(self))
+    if (manager_is_disabled(self))
         return;
 
     _LOGD(LOGD_RFKILL,
@@ -4079,7 +4085,7 @@ add_device(NMManager *self, NMDevice *device, GError **error)
 
     nm_device_set_unmanaged_by_user_settings(device, TRUE);
 
-    nm_device_set_unmanaged_flags(device, NM_UNMANAGED_SLEEPING, manager_sleeping(self));
+    nm_device_set_unmanaged_flags(device, NM_UNMANAGED_MANAGER_DISABLED, manager_is_disabled(self));
 
     dbus_path = nm_dbus_object_export(NM_DBUS_OBJECT(device));
     _LOG2I(LOGD_DEVICE, device, "new %s device (%s)", type_desc, dbus_path);
@@ -7299,7 +7305,7 @@ device_sleep_cb(NMDevice *device, GParamSpec *pspec, NMManager *self)
     case NM_DEVICE_STATE_DISCONNECTED:
         _LOGD(LOGD_SUSPEND, "sleep: unmanaging device %s", nm_device_get_ip_iface(device));
         nm_device_set_unmanaged_by_flags_queue(device,
-                                               NM_UNMANAGED_SLEEPING,
+                                               NM_UNMANAGED_MANAGER_DISABLED,
                                                NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                                NM_DEVICE_STATE_REASON_SLEEPING);
         break;
@@ -7321,24 +7327,26 @@ _handle_device_takedown(NMManager *self,
                         gboolean   suspending,
                         gboolean   is_shutdown)
 {
+    gboolean            is_sleep = suspending || is_shutdown;
+    NMDeviceStateReason reason =
+        is_sleep ? NM_DEVICE_STATE_REASON_SLEEPING : NM_DEVICE_STATE_REASON_NETWORKING_OFF;
+
     nm_device_notify_sleeping(device);
 
     if (nm_device_is_activating(device)
         || nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) {
-        _LOGD(LOGD_SUSPEND,
+        _LOGD(is_sleep ? LOGD_SUSPEND : LOGD_CORE,
               "%s: wait disconnection of device %s",
-              is_shutdown ? "shutdown" : "sleep",
+              is_sleep ? (is_shutdown ? "shutdown" : "sleep") : "networking off",
               nm_device_get_ip_iface(device));
 
         if (sleep_devices_add(self, device, suspending))
-            nm_device_queue_state(device,
-                                  NM_DEVICE_STATE_DEACTIVATING,
-                                  NM_DEVICE_STATE_REASON_SLEEPING);
+            nm_device_queue_state(device, NM_DEVICE_STATE_DEACTIVATING, reason);
     } else {
         nm_device_set_unmanaged_by_flags(device,
-                                         NM_UNMANAGED_SLEEPING,
+                                         NM_UNMANAGED_MANAGER_DISABLED,
                                          NM_UNMAN_FLAG_OP_SET_UNMANAGED,
-                                         NM_DEVICE_STATE_REASON_SLEEPING);
+                                         reason);
     }
 }
 
@@ -7352,8 +7360,10 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
     suspending          = sleeping_changed && priv->sleeping;
     waking_from_suspend = sleeping_changed && !priv->sleeping;
 
-    if (manager_sleeping(self)) {
-        _LOGD(LOGD_SUSPEND, "sleep: %s...", suspending ? "sleeping" : "disabling");
+    if (manager_is_disabled(self)) {
+        _LOGD(suspending ? LOGD_SUSPEND : LOGD_CORE,
+              "%s...",
+              suspending ? "sleep: sleeping" : "networking: disabling");
 
         /* FIXME: are there still hardware devices that need to be disabled around
          * suspend/resume?
@@ -7379,7 +7389,9 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
             _handle_device_takedown(self, device, suspending, FALSE);
         }
     } else {
-        _LOGD(LOGD_SUSPEND, "sleep: %s...", waking_from_suspend ? "waking up" : "re-enabling");
+        _LOGD(waking_from_suspend ? LOGD_SUSPEND : LOGD_CORE,
+              "%s...",
+              waking_from_suspend ? "sleep: waking up" : "networking: re-enabling");
 
         sleep_devices_clear(self);
 
@@ -7393,7 +7405,7 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
                  */
                 if (device_is_wake_on_lan(priv->platform, device))
                     nm_device_set_unmanaged_by_flags(device,
-                                                     NM_UNMANAGED_SLEEPING,
+                                                     NM_UNMANAGED_MANAGER_DISABLED,
                                                      NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                                      NM_DEVICE_STATE_REASON_SLEEPING);
 
@@ -7421,10 +7433,12 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
             guint               i;
 
             if (nm_device_is_software(device)
-                && !nm_device_get_unmanaged_flags(device, NM_UNMANAGED_SLEEPING)) {
+                && !nm_device_get_unmanaged_flags(device, NM_UNMANAGED_MANAGER_DISABLED)) {
                 /* DHCP leases of software devices could have gone stale
                  * so we need to renew them. */
-                nm_device_update_dynamic_ip_setup(device, "wake up");
+                nm_device_update_dynamic_ip_setup(device,
+                                                  waking_from_suspend ? "wake up"
+                                                                      : "networking on");
                 continue;
             }
 
@@ -7455,7 +7469,7 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
                     ? NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED
                     : NM_DEVICE_STATE_REASON_NOW_MANAGED;
             nm_device_set_unmanaged_by_flags(device,
-                                             NM_UNMANAGED_SLEEPING,
+                                             NM_UNMANAGED_MANAGER_DISABLED,
                                              NM_UNMAN_FLAG_OP_SET_MANAGED,
                                              reason);
         }
@@ -8818,6 +8832,41 @@ nm_manager_emit_device_ifindex_changed(NMManager *self, NMDevice *device)
     g_signal_emit(self, signals[DEVICE_IFINDEX_CHANGED], 0, device);
 }
 
+void
+nm_manager_update_shared_connection(NMManager *self, int addr_family, gboolean enabled)
+{
+    NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self);
+    gboolean          state_changed, state;
+
+    /* Only IPv4 supported for the moment */
+    if (addr_family != AF_INET)
+        return;
+
+    if (enabled) {
+        g_return_if_fail(priv->shared_connections_ip4_count < G_MAXUINT);
+        priv->shared_connections_ip4_count++;
+        state_changed = priv->shared_connections_ip4_count == 1;
+    } else {
+        g_return_if_fail(priv->shared_connections_ip4_count > 0);
+        priv->shared_connections_ip4_count--;
+        state_changed = priv->shared_connections_ip4_count == 0;
+    }
+
+    if (state_changed) {
+        state = priv->shared_connections_ip4_count > 0;
+        _LOGD(LOGD_SHARING, "sharing-ipv4 state change %d -> %d", !state, state);
+        g_signal_emit(self, signals[SHARING_IPV4_CHANGED], 0, state);
+    }
+}
+
+gboolean
+nm_manager_get_sharing_ipv4(NMManager *self)
+{
+    NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self);
+
+    return priv->shared_connections_ip4_count > 0;
+}
+
 /*****************************************************************************/
 
 NM_DEFINE_SINGLETON_REGISTER(NMManager);
@@ -9921,6 +9970,17 @@ nm_manager_class_init(NMManagerClass *manager_class)
                                                    G_TYPE_NONE,
                                                    1,
                                                    NM_TYPE_DEVICE);
+
+    signals[SHARING_IPV4_CHANGED] = g_signal_new(NM_MANAGER_SHARING_IPV4_CHANGED,
+                                                 G_OBJECT_CLASS_TYPE(object_class),
+                                                 G_SIGNAL_RUN_FIRST,
+                                                 0,
+                                                 NULL,
+                                                 NULL,
+                                                 NULL,
+                                                 G_TYPE_NONE,
+                                                 1,
+                                                 G_TYPE_BOOLEAN);
 }
 
 NMConfig *
diff --git a/src/core/nm-manager.h b/src/core/nm-manager.h
index 3c5213c4..e10ca0d1 100644
--- a/src/core/nm-manager.h
+++ b/src/core/nm-manager.h
@@ -61,6 +61,7 @@
 #define NM_MANAGER_CONFIGURE_QUIT            "configure-quit"
 #define NM_MANAGER_INTERNAL_DEVICE_ADDED     "internal-device-added"
 #define NM_MANAGER_INTERNAL_DEVICE_REMOVED   "internal-device-removed"
+#define NM_MANAGER_SHARING_IPV4_CHANGED      "sharing-ipv4-changed"
 
 GType nm_manager_get_type(void);
 
@@ -212,6 +213,9 @@ struct _NMDnsManager;
 
 struct _NMDnsManager *nm_manager_get_dns_manager(NMManager *self);
 
+void     nm_manager_update_shared_connection(NMManager *self, int addr_family, gboolean enabled);
+gboolean nm_manager_get_sharing_ipv4(NMManager *self);
+
 /*****************************************************************************/
 
 void nm_manager_notify_delete_settings_connections(NMManager            *self,
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
index fbcee40d..0288795e 100644
--- a/src/core/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -18,7 +18,6 @@
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-factory.h"
-#include "devices/nm-device-private.h"
 #include "dns/nm-dns-manager.h"
 #include "nm-act-request.h"
 #include "nm-auth-utils.h"
@@ -98,6 +97,7 @@ typedef struct {
     bool updating_dns : 1;
 
     GArray *ip6_prefix_delegations; /* pool of ip6 prefixes delegated to all devices */
+
 } NMPolicyPrivate;
 
 struct _NMPolicy {
@@ -1845,7 +1845,7 @@ nm_policy_device_recheck_auto_activate_schedule(NMPolicy *self, NMDevice *device
 
     priv = NM_POLICY_GET_PRIVATE(self);
 
-    if (nm_manager_get_state(priv->manager) == NM_STATE_ASLEEP)
+    if (nm_manager_get_state(priv->manager) == NM_STATE_DISABLED)
         return;
 
     if (!nm_device_autoconnect_allowed(device))
@@ -2084,65 +2084,6 @@ unblock_autoconnect_for_ports_for_sett_conn(NMPolicy *self, NMSettingsConnection
 }
 
 static void
-refresh_forwarding(NMPolicy *self, NMDevice *device, gboolean is_activated_shared_device)
-{
-    NMActiveConnection *ac;
-    NMDevice           *tmp_device;
-    NMPolicyPrivate    *priv = NM_POLICY_GET_PRIVATE(self);
-    const CList        *tmp_lst;
-    gboolean            any_shared_active = false;
-    gint32              default_forwarding_v4;
-    const char         *new_value = NULL;
-
-    /* FIXME: This implementation is still inefficient because refresh_forwarding()
-     * is called every time a device goes up or down, requiring a full scan of all
-     * active connections to determine if any shared connection is active. */
-    nm_manager_for_each_active_connection (priv->manager, ac, tmp_lst) {
-        NMSettingIPConfig *s_ip;
-        NMDevice          *to_device = nm_active_connection_get_device(ac);
-
-        if (to_device) {
-            s_ip = nm_device_get_applied_setting(to_device, NM_TYPE_SETTING_IP4_CONFIG);
-            if (s_ip) {
-                if (nm_streq0(nm_device_get_effective_ip_config_method(to_device, AF_INET),
-                              NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
-                    any_shared_active = true;
-                    break;
-                }
-            }
-        }
-    }
-
-    default_forwarding_v4 = nm_platform_sysctl_get_int32(
-        NM_PLATFORM_GET,
-        NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/conf/default/forwarding"),
-        0);
-
-    new_value = any_shared_active ? "1" : (default_forwarding_v4 ? "1" : "0");
-
-    nm_manager_for_each_device (priv->manager, tmp_device, tmp_lst) {
-        NMDeviceState               state;
-        NMSettingIPConfigForwarding ipv4_forwarding;
-
-        state = nm_device_get_state(tmp_device);
-        if (state != NM_DEVICE_STATE_ACTIVATED)
-            continue;
-
-        ipv4_forwarding = nm_device_get_ipv4_forwarding(tmp_device);
-
-        if (ipv4_forwarding == NM_SETTING_IP_CONFIG_FORWARDING_AUTO
-            || (device == tmp_device && is_activated_shared_device)) {
-            gs_free char *sysctl_value = NULL;
-
-            sysctl_value = nm_device_sysctl_ip_conf_get(tmp_device, AF_INET, "forwarding");
-
-            if (!nm_streq0(sysctl_value, new_value))
-                nm_device_sysctl_ip_conf_set(tmp_device, AF_INET, "forwarding", new_value);
-        }
-    }
-}
-
-static void
 activate_port_or_children_connections(NMPolicy *self,
                                       NMDevice *device,
                                       gboolean  activate_children_connections_only)
@@ -2286,9 +2227,8 @@ device_state_changed(NMDevice           *device,
     NMPolicyPrivate      *priv = user_data;
     NMPolicy             *self = _PRIV_TO_SELF(priv);
     NMActiveConnection   *ac;
-    NMSettingsConnection *sett_conn                  = nm_device_get_settings_connection(device);
-    NMSettingConnection  *s_con                      = NULL;
-    gboolean              is_activated_shared_device = FALSE;
+    NMSettingsConnection *sett_conn = nm_device_get_settings_connection(device);
+    NMSettingConnection  *s_con     = NULL;
 
     switch (nm_device_state_reason_check(reason)) {
     case NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED:
@@ -2404,10 +2344,6 @@ device_state_changed(NMDevice           *device,
                 }
             }
         }
-        if (!nm_device_get_refresh_forwarding_done(device)) {
-            refresh_forwarding(self, device, FALSE);
-            nm_device_set_refresh_forwarding_done(device, TRUE);
-        }
         break;
     case NM_DEVICE_STATE_ACTIVATED:
         if (nm_device_get_device_type(device) == NM_DEVICE_TYPE_OVS_INTERFACE) {
@@ -2440,20 +2376,11 @@ device_state_changed(NMDevice           *device,
         update_system_hostname(self, "routing and dns", TRUE);
         nm_dns_manager_end_updates(priv->dns_manager, __func__);
 
-        is_activated_shared_device =
-            nm_streq0(nm_device_get_effective_ip_config_method(device, AF_INET),
-                      NM_SETTING_IP4_CONFIG_METHOD_SHARED);
-        refresh_forwarding(self, device, is_activated_shared_device);
-        nm_device_set_refresh_forwarding_done(device, FALSE);
         break;
     case NM_DEVICE_STATE_UNMANAGED:
     case NM_DEVICE_STATE_UNAVAILABLE:
         if (old_state > NM_DEVICE_STATE_DISCONNECTED)
             update_routing_and_dns(self, FALSE, device);
-        if (!nm_device_get_refresh_forwarding_done(device)) {
-            refresh_forwarding(self, device, FALSE);
-            nm_device_set_refresh_forwarding_done(device, TRUE);
-        }
         break;
     case NM_DEVICE_STATE_DEACTIVATING:
         if (sett_conn) {
@@ -2489,10 +2416,6 @@ device_state_changed(NMDevice           *device,
             }
         }
         ip6_remove_device_prefix_delegations(self, device);
-        if (!nm_device_get_refresh_forwarding_done(device)) {
-            refresh_forwarding(self, device, FALSE);
-            nm_device_set_refresh_forwarding_done(device, TRUE);
-        }
         break;
     case NM_DEVICE_STATE_DISCONNECTED:
         g_signal_handlers_disconnect_by_func(device, device_dns_lookup_done, self);
@@ -2509,10 +2432,6 @@ device_state_changed(NMDevice           *device,
 
         /* Device is now available for auto-activation */
         nm_policy_device_recheck_auto_activate_schedule(self, device);
-        if (!nm_device_get_refresh_forwarding_done(device)) {
-            refresh_forwarding(self, device, FALSE);
-            nm_device_set_refresh_forwarding_done(device, TRUE);
-        }
         break;
 
     case NM_DEVICE_STATE_PREPARE:
@@ -2528,10 +2447,6 @@ device_state_changed(NMDevice           *device,
             g_object_weak_unref(G_OBJECT(ac), pending_ac_gone, self);
             g_object_unref(self);
         }
-        if (!nm_device_get_refresh_forwarding_done(device)) {
-            refresh_forwarding(self, device, FALSE);
-            nm_device_set_refresh_forwarding_done(device, TRUE);
-        }
         break;
     case NM_DEVICE_STATE_IP_CONFIG:
         /* We must have secrets if we got here. */
@@ -2542,10 +2457,6 @@ device_state_changed(NMDevice           *device,
                 sett_conn,
                 NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
                 FALSE);
-        if (!nm_device_get_refresh_forwarding_done(device)) {
-            refresh_forwarding(self, device, FALSE);
-            nm_device_set_refresh_forwarding_done(device, TRUE);
-        }
         break;
     case NM_DEVICE_STATE_SECONDARIES:
         if (sett_conn)
diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver
index ad05a4e2..040d6829 100644
--- a/src/libnm-client-impl/libnm.ver
+++ b/src/libnm-client-impl/libnm.ver
@@ -2076,3 +2076,10 @@ global:
 	nm_setting_sriov_get_preserve_on_down;
 	nm_sriov_preserve_on_down_get_type;
 } libnm_1_52_0;
+
+libnm_1_54_2 {
+global:
+	nm_setting_hsr_get_interlink;
+	nm_setting_hsr_get_protocol_version;
+	nm_setting_hsr_protocol_version_get_type;
+} libnm_1_54_0;
diff --git a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
index c764f535..ce4832dc 100644
--- a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
+++ b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
@@ -1516,6 +1516,10 @@
     <setting name="hsr"
              gtype="NMSettingHsr"
              >
+        <property name="interlink"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
         <property name="multicast-spec"
                   dbus-type="u"
                   gprop-type="guint"
@@ -1528,6 +1532,10 @@
                   dbus-type="s"
                   gprop-type="gchararray"
                   />
+        <property name="protocol-version"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
         <property name="prp"
                   dbus-type="b"
                   gprop-type="gboolean"
diff --git a/src/libnm-core-impl/nm-setting-hsr.c b/src/libnm-core-impl/nm-setting-hsr.c
index d9a662b1..1edf4a14 100644
--- a/src/libnm-core-impl/nm-setting-hsr.c
+++ b/src/libnm-core-impl/nm-setting-hsr.c
@@ -23,12 +23,20 @@
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE(NMSettingHsr, PROP_PORT1, PROP_PORT2, PROP_MULTICAST_SPEC, PROP_PRP, );
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettingHsr,
+                             PROP_PORT1,
+                             PROP_PORT2,
+                             PROP_MULTICAST_SPEC,
+                             PROP_PRP,
+                             PROP_PROTOCOL_VERSION,
+                             PROP_INTERLINK, );
 
 typedef struct {
     char   *port1;
     char   *port2;
+    char   *interlink;
     guint32 multicast_spec;
+    int     protocol_version;
     bool    prp;
 } NMSettingHsrPrivate;
 
@@ -117,6 +125,38 @@ nm_setting_hsr_get_prp(NMSettingHsr *setting)
     return NM_SETTING_HSR_GET_PRIVATE(setting)->prp;
 }
 
+/**
+ * nm_setting_hsr_get_protocol_version:
+ * @setting: the #NMSettingHsr
+ *
+ * Returns: the #NMSettingHsr:protocol-version property of the setting
+ *
+ * Since: 1.56, 1.54.2
+ **/
+NMSettingHsrProtocolVersion
+nm_setting_hsr_get_protocol_version(NMSettingHsr *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_HSR(setting), NM_SETTING_HSR_PROTOCOL_VERSION_DEFAULT);
+
+    return NM_SETTING_HSR_GET_PRIVATE(setting)->protocol_version;
+}
+
+/**
+ * nm_setting_hsr_get_interlink:
+ * @setting: the #NMSettingHsr
+ *
+ * Returns: the #NMSettingHsr:interlink property of the setting
+ *
+ * Since: 1.56, 1.54.2
+ **/
+const char *
+nm_setting_hsr_get_interlink(NMSettingHsr *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_HSR(setting), NULL);
+
+    return NM_SETTING_HSR_GET_PRIVATE(setting)->interlink;
+}
+
 /*****************************************************************************/
 
 static gboolean
@@ -160,6 +200,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         return FALSE;
     }
 
+    if (priv->prp && priv->protocol_version != NM_SETTING_HSR_PROTOCOL_VERSION_DEFAULT) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("HSR protocol cannot be configured for PRP interfaces"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_HSR_SETTING_NAME,
+                       NM_SETTING_HSR_PROTOCOL_VERSION);
+        return FALSE;
+    }
+
     return TRUE;
 }
 
@@ -260,6 +312,42 @@ nm_setting_hsr_class_init(NMSettingHsrClass *klass)
                                                NMSettingHsr,
                                                _priv.prp);
 
+    /**
+      * NMSettingHsr:protocol-version:
+      *
+      * Configures the protocol version to be used for the HSR/PRP interface.
+      * %NM_SETTING_HSR_PROTOCOL_VERSION_DEFAULT sets the protocol version to the default version for the protocol.
+      * %NM_SETTING_HSR_PROTOCOL_VERSION_HSR_2010 sets the protocol version to HSRv0 (IEC 62439-3:2010).
+      * %NM_SETTING_HSR_PROTOCOL_VERSION_HSR_2012 sets the protocol version to HSRv1 (IEC 62439-3:2012).
+      *
+      * Since: 1.56, 1.54.2
+      **/
+    _nm_setting_property_define_direct_enum(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_HSR_PROTOCOL_VERSION,
+                                            PROP_PROTOCOL_VERSION,
+                                            NM_TYPE_SETTING_HSR_PROTOCOL_VERSION,
+                                            NM_SETTING_HSR_PROTOCOL_VERSION_DEFAULT,
+                                            NM_SETTING_PARAM_NONE,
+                                            NULL,
+                                            NMSettingHsr,
+                                            _priv.protocol_version);
+
+    /**
+      * NMSettingHsr:interlink:
+      *
+      * The optional interlink port name of the HSR interface.
+      *
+      * Since: 1.56, 1.54.2
+      **/
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_HSR_INTERLINK,
+                                              PROP_INTERLINK,
+                                              NM_SETTING_PARAM_INFERRABLE,
+                                              NMSettingHsr,
+                                              _priv.interlink);
+
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
     _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_HSR, NULL, properties_override, 0);
diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h
index 78241193..e62d64e1 100644
--- a/src/libnm-core-public/nm-dbus-interface.h
+++ b/src/libnm-core-public/nm-dbus-interface.h
@@ -145,8 +145,10 @@ typedef enum {
  *    and not disable controls that require network access.
  *    The graphical shells may hide the network accessibility indicator altogether
  *    since no meaningful status indication can be provided.
- * @NM_STATE_ASLEEP: Networking is not enabled, the system is being suspended or
- *    resumed from suspend.
+ * @NM_STATE_ASLEEP: Deprecated: 1.56: Use %NM_STATE_DISABLED instead.
+ * @NM_STATE_DISABLED: NetworkManager is disabled, either because the user requested
+ *    to disable networking or because the system is suspended or resuming from suspend.
+ *    Since: 1.56.
  * @NM_STATE_DISCONNECTED: There is no active network connection.
  *    The graphical shell should indicate  no network connectivity and the
  *    applications should not attempt to access the network.
@@ -170,7 +172,8 @@ typedef enum {
  **/
 typedef enum {
     NM_STATE_UNKNOWN          = 0,
-    NM_STATE_ASLEEP           = 10,
+    NM_STATE_ASLEEP           = 10, /* Deprecated */
+    NM_STATE_DISABLED         = 10,
     NM_STATE_DISCONNECTED     = 20,
     NM_STATE_DISCONNECTING    = 30,
     NM_STATE_CONNECTING       = 40,
@@ -632,8 +635,10 @@ typedef enum {
  *   not initialized by udev. Since: 1.48
  * @NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING: The device is unmanaged because NetworkManager is
  *   quitting. Since: 1.48
- * @NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING: The device is unmanaged because networking is
- *   disabled or the system is suspended. Since: 1.48
+ * @NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING: Since: 1.48. Deprecated: 1.56: Use
+ *   %NM_DEVICE_STATE_REASON_UNMANAGED_MANAGER_DISABLED instead.
+ * @NM_DEVICE_STATE_REASON_UNMANAGED_MANAGER_DISABLED: The device is unmanaged because networking is
+ *   disabled or the system is suspended. Since: 1.56
  * @NM_DEVICE_STATE_REASON_UNMANAGED_USER_CONF: The device is unmanaged by user decision in
  *   NetworkManager.conf ('unmanaged' in a [device*] section). Since: 1.48
  * @NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT: The device is unmanaged by explicit user
@@ -642,7 +647,7 @@ typedef enum {
  *   via settings plugin ('unmanaged-devices' for keyfile or 'NM_CONTROLLED=no' for ifcfg-rh).
  *   Since: 1.48
  * @NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV: The device is unmanaged via udev rule. Since: 1.48
-
+ * @NM_DEVICE_STATE_REASON_NETWORKING_OFF: NetworkManager was disabled (networking off). Since: 1.56
  *
  * Device state change reason codes
  */
@@ -720,11 +725,13 @@ typedef enum {
     NM_DEVICE_STATE_REASON_UNMANAGED_EXTERNAL_DOWN        = 70,
     NM_DEVICE_STATE_REASON_UNMANAGED_LINK_NOT_INIT        = 71,
     NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING             = 72,
-    NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING             = 73,
+    NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING             = 73, /* Deprecated */
+    NM_DEVICE_STATE_REASON_UNMANAGED_MANAGER_DISABLED     = 73,
     NM_DEVICE_STATE_REASON_UNMANAGED_USER_CONF            = 74,
     NM_DEVICE_STATE_REASON_UNMANAGED_USER_EXPLICIT        = 75,
     NM_DEVICE_STATE_REASON_UNMANAGED_USER_SETTINGS        = 76,
     NM_DEVICE_STATE_REASON_UNMANAGED_USER_UDEV            = 77,
+    NM_DEVICE_STATE_REASON_NETWORKING_OFF                 = 78,
 } NMDeviceStateReason;
 
 /**
diff --git a/src/libnm-core-public/nm-setting-hsr.h b/src/libnm-core-public/nm-setting-hsr.h
index f7b0136f..414f2b34 100644
--- a/src/libnm-core-public/nm-setting-hsr.h
+++ b/src/libnm-core-public/nm-setting-hsr.h
@@ -23,10 +23,28 @@ G_BEGIN_DECLS
 
 #define NM_SETTING_HSR_SETTING_NAME "hsr"
 
-#define NM_SETTING_HSR_PORT1          "port1"
-#define NM_SETTING_HSR_PORT2          "port2"
-#define NM_SETTING_HSR_MULTICAST_SPEC "multicast-spec"
-#define NM_SETTING_HSR_PRP            "prp"
+#define NM_SETTING_HSR_PORT1            "port1"
+#define NM_SETTING_HSR_PORT2            "port2"
+#define NM_SETTING_HSR_MULTICAST_SPEC   "multicast-spec"
+#define NM_SETTING_HSR_PRP              "prp"
+#define NM_SETTING_HSR_PROTOCOL_VERSION "protocol-version"
+#define NM_SETTING_HSR_INTERLINK        "interlink"
+
+/**
+ * NMSettingHsrProtocolVersion:
+ * @NM_SETTING_HSR_PROTOCOL_VERSION_DEFAULT: Default version for the protocol
+ * @NM_SETTING_HSR_PROTOCOL_VERSION_HSR_2010: HSRv0, IEC 62439-3:2010
+ * @NM_SETTING_HSR_PROTOCOL_VERSION_HSR_2012: HSRv1, IEC 62439-3:2012
+ *
+ * #NMSettingHsrProtocolVersion values indicate the HSR protocol version.
+ *
+ * Since: 1.56, 1.54.2
+ */
+typedef enum {
+    NM_SETTING_HSR_PROTOCOL_VERSION_DEFAULT  = -1,
+    NM_SETTING_HSR_PROTOCOL_VERSION_HSR_2010 = 0,
+    NM_SETTING_HSR_PROTOCOL_VERSION_HSR_2012 = 1,
+} NMSettingHsrProtocolVersion;
 
 typedef struct _NMSettingHsrClass NMSettingHsrClass;
 
@@ -43,6 +61,10 @@ NM_AVAILABLE_IN_1_46
 guint32 nm_setting_hsr_get_multicast_spec(NMSettingHsr *setting);
 NM_AVAILABLE_IN_1_46
 gboolean nm_setting_hsr_get_prp(NMSettingHsr *setting);
+NM_AVAILABLE_IN_1_54_2
+NMSettingHsrProtocolVersion nm_setting_hsr_get_protocol_version(NMSettingHsr *setting);
+NM_AVAILABLE_IN_1_54_2
+const char *nm_setting_hsr_get_interlink(NMSettingHsr *setting);
 
 G_END_DECLS
 
diff --git a/src/libnm-core-public/nm-version-macros.h.in b/src/libnm-core-public/nm-version-macros.h.in
index 4c4772d2..4bdf6716 100644
--- a/src/libnm-core-public/nm-version-macros.h.in
+++ b/src/libnm-core-public/nm-version-macros.h.in
@@ -78,6 +78,7 @@
 #define NM_VERSION_1_50   (NM_ENCODE_VERSION(1, 50, 0))
 #define NM_VERSION_1_52   (NM_ENCODE_VERSION(1, 52, 0))
 #define NM_VERSION_1_54   (NM_ENCODE_VERSION(1, 54, 0))
+#define NM_VERSION_1_54_2 (NM_ENCODE_VERSION(1, 54, 2))
 
 /* For releases, NM_API_VERSION is equal to NM_VERSION.
  *
diff --git a/src/libnm-core-public/nm-version.h b/src/libnm-core-public/nm-version.h
index 33daf65f..f8f49df2 100644
--- a/src/libnm-core-public/nm-version.h
+++ b/src/libnm-core-public/nm-version.h
@@ -439,6 +439,12 @@
 #define NM_AVAILABLE_IN_1_54
 #endif
 
+#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_54_2
+#define NM_AVAILABLE_IN_1_54_2 G_UNAVAILABLE(1, 54.2)
+#else
+#define NM_AVAILABLE_IN_1_54_2
+#endif
+
 /*
  * Synchronous API for calling D-Bus in libnm is deprecated. See
  * https://networkmanager.dev/docs/libnm/latest/usage.html#sync-api
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index d45893fe..0c1097c2 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -253,7 +253,8 @@ G_STATIC_ASSERT(RTA_MAX == (__RTA_MAX - 1));
 #define IFLA_HSR_SEQ_NR           5
 #define IFLA_HSR_VERSION          6
 #define IFLA_HSR_PROTOCOL         7
-#define __IFLA_HSR_MAX            8
+#define IFLA_HSR_INTERLINK        8
+#define __IFLA_HSR_MAX            9
 
 /*****************************************************************************/
 
@@ -5196,6 +5197,15 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo
             NLA_PUT_U8(msg, IFLA_HSR_MULTICAST_SPEC, props->multicast_spec);
 
         NLA_PUT_U8(msg, IFLA_HSR_PROTOCOL, props->prp);
+
+        if (!props->prp && props->protocol_version >= 0) {
+            NLA_PUT_U8(msg, IFLA_HSR_VERSION, props->protocol_version);
+        }
+
+        if (props->interlink > 0) {
+            NLA_PUT_U32(msg, IFLA_HSR_INTERLINK, props->interlink);
+        }
+
         break;
     }
     case NM_LINK_TYPE_SIT:
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index fd966d2c..33c763c3 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -6560,6 +6560,8 @@ nm_platform_lnk_gre_to_string(const NMPlatformLnkGre *lnk, char *buf, gsize len)
 const char *
 nm_platform_lnk_hsr_to_string(const NMPlatformLnkHsr *lnk, char *buf, gsize len)
 {
+    char interlink_buf[30];
+
     if (!nm_utils_to_string_buffer_init_null(lnk, &buf, &len))
         return buf;
 
@@ -6569,12 +6571,17 @@ nm_platform_lnk_hsr_to_string(const NMPlatformLnkHsr *lnk, char *buf, gsize len)
                "port1 %d "
                "port2 %d "
                "supervision_address " NM_ETHER_ADDR_FORMAT_STR " multicast_spec %u "
-               "prp %s",
+               "prp %s "
+               "protocol_version %d"
+               "%s", /* interlink */
                lnk->port1,
                lnk->port2,
                NM_ETHER_ADDR_FORMAT_VAL(&lnk->supervision_address),
                lnk->multicast_spec,
-               lnk->prp ? "on" : "off");
+               lnk->prp ? "on" : "off",
+               lnk->protocol_version,
+               lnk->interlink ? nm_sprintf_buf(interlink_buf, " interlink %d", lnk->interlink)
+                              : "");
     return buf;
 }
 
@@ -8488,7 +8495,9 @@ nm_platform_lnk_hsr_hash_update(const NMPlatformLnkHsr *obj, NMHashState *h)
                         obj->port2,
                         obj->supervision_address,
                         obj->multicast_spec,
-                        NM_HASH_COMBINE_BOOLS(guint8, obj->prp));
+                        NM_HASH_COMBINE_BOOLS(guint8, obj->prp),
+                        obj->protocol_version,
+                        obj->interlink);
 }
 
 int
@@ -8500,6 +8509,8 @@ nm_platform_lnk_hsr_cmp(const NMPlatformLnkHsr *a, const NMPlatformLnkHsr *b)
     NM_CMP_FIELD_MEMCMP(a, b, supervision_address);
     NM_CMP_FIELD(a, b, multicast_spec);
     NM_CMP_FIELD_BOOL(a, b, prp);
+    NM_CMP_FIELD(a, b, protocol_version);
+    NM_CMP_FIELD(a, b, interlink);
     return 0;
 }
 
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index e4ccb9b1..a63f40c7 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -845,7 +845,9 @@ typedef struct {
 typedef struct {
     int         port1;
     int         port2;
+    int         interlink;
     NMEtherAddr supervision_address;
+    gint8       protocol_version;
     guint8      multicast_spec;
     bool        prp : 1;
 } _nm_alignas(NMPlatformObject) NMPlatformLnkHsr;
diff --git a/src/libnmc-base/nm-client-utils.c b/src/libnmc-base/nm-client-utils.c
index 0c4d53a4..326d8d20 100644
--- a/src/libnmc-base/nm-client-utils.c
+++ b/src/libnmc-base/nm-client-utils.c
@@ -467,7 +467,7 @@ NM_UTILS_LOOKUP_STR_DEFINE(
                          N_("The Wi-Fi P2P peer could not be found")),
     NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_DEVICE_HANDLER_FAILED,
                          N_("The device handler dispatcher returned an error")),
-    NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_SLEEPING,
+    NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_MANAGER_DISABLED,
                          N_("The device is unmanaged because networking is disabled "
                             "or the system is suspended")),
     NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_QUITTING,
@@ -492,6 +492,7 @@ NM_UTILS_LOOKUP_STR_DEFINE(
     NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_UNMANAGED_EXTERNAL_DOWN,
                          N_("The device is unmanaged because it is an external device and is "
                             "unconfigured (down or without addresses)")),
+    NM_UTILS_LOOKUP_ITEM(NM_DEVICE_STATE_REASON_NETWORKING_OFF, N_("Networking was disabled")),
 
 );
 
diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c
index aa290b9f..c035ad54 100644
--- a/src/libnmc-setting/nm-meta-setting-desc.c
+++ b/src/libnmc-setting/nm-meta-setting-desc.c
@@ -6198,6 +6198,12 @@ static const NMMetaPropertyInfo *const property_infos_HSR[] = {
     PROPERTY_INFO_WITH_DESC (NM_SETTING_HSR_PRP,
         .property_type =                &_pt_gobject_bool,
     ),
+    PROPERTY_INFO_WITH_DESC (NM_SETTING_HSR_PROTOCOL_VERSION,
+        .property_type =                &_pt_gobject_enum,
+    ),
+    PROPERTY_INFO_WITH_DESC (NM_SETTING_HSR_INTERLINK,
+        .property_type =                &_pt_gobject_string,
+    ),
     NULL
 };
 
diff --git a/src/libnmc-setting/nm-meta-setting-desc.h b/src/libnmc-setting/nm-meta-setting-desc.h
index 0294b1f7..9efbd15c 100644
--- a/src/libnmc-setting/nm-meta-setting-desc.h
+++ b/src/libnmc-setting/nm-meta-setting-desc.h
@@ -116,7 +116,8 @@ typedef enum {
     NM_META_COLOR_PERMISSION_UNKNOWN,
     NM_META_COLOR_PERMISSION_YES,
     NM_META_COLOR_PROMPT,
-    NM_META_COLOR_STATE_ASLEEP,
+    NM_META_COLOR_STATE_DISABLED,
+    NM_META_COLOR_STATE_ASLEEP = NM_META_COLOR_STATE_DISABLED, /* Deprecated */
     NM_META_COLOR_STATE_CONNECTED_GLOBAL,
     NM_META_COLOR_STATE_CONNECTED_LOCAL,
     NM_META_COLOR_STATE_CONNECTED_SITE,
diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in
index 56c7c29c..ce9f27f9 100644
--- a/src/libnmc-setting/settings-docs.h.in
+++ b/src/libnmc-setting/settings-docs.h.in
@@ -171,9 +171,11 @@
 #define DESCRIBE_DOC_NM_SETTING_GSM_SIM_ID N_("The SIM card unique identifier (as given by the WWAN management service) which this connection applies to.  If given, the connection will apply to any device also allowed by \"device-id\" which contains a SIM card matching the given identifier.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_SIM_OPERATOR_ID N_("A MCC/MNC string like \"310260\" or \"21601\" identifying the specific mobile network operator which this connection applies to.  If given, the connection will apply to any device also allowed by \"device-id\" and \"sim-id\" which contains a SIM card provisioned by the given operator.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_USERNAME N_("The username used to authenticate with the network, if required.  Many providers do not require a username, or accept any username.  But if a username is required, it is specified here.")
+#define DESCRIBE_DOC_NM_SETTING_HSR_INTERLINK N_("The optional interlink port name of the HSR interface.")
 #define DESCRIBE_DOC_NM_SETTING_HSR_MULTICAST_SPEC N_("The last byte of supervision address.")
 #define DESCRIBE_DOC_NM_SETTING_HSR_PORT1 N_("The port1 interface name of the HSR. This property is mandatory.")
 #define DESCRIBE_DOC_NM_SETTING_HSR_PORT2 N_("The port2 interface name of the HSR. This property is mandatory.")
+#define DESCRIBE_DOC_NM_SETTING_HSR_PROTOCOL_VERSION N_("Configures the protocol version to be used for the HSR/PRP interface. \"default\" (-1) sets the protocol version to the default version for the protocol. \"hsr-2010\" (0) sets the protocol version to HSRv0 (IEC 62439-3:2010). \"hsr-2012\" (1) sets the protocol version to HSRv1 (IEC 62439-3:2012).")
 #define DESCRIBE_DOC_NM_SETTING_HSR_PRP N_("The protocol used by the interface, whether it is PRP or HSR.")
 #define DESCRIBE_DOC_NM_SETTING_INFINIBAND_MAC_ADDRESS N_("If specified, this connection will only apply to the IPoIB device whose permanent MAC address matches. This property does not change the MAC address of the device (i.e. MAC spoofing).")
 #define DESCRIBE_DOC_NM_SETTING_INFINIBAND_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames.")
diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
index 67b14d4c..854a0227 100644
--- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
+++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
@@ -1235,6 +1235,13 @@
                   nmcli-description="The protocol used by the interface, whether it is PRP or HSR."
                   format="boolean"
                   values="true/yes/on, false/no/off" />
+        <property name="protocol-version"
+                  nmcli-description="Configures the protocol version to be used for the HSR/PRP interface. &quot;default&quot; (-1) sets the protocol version to the default version for the protocol. &quot;hsr-2010&quot; (0) sets the protocol version to HSRv0 (IEC 62439-3:2010). &quot;hsr-2012&quot; (1) sets the protocol version to HSRv1 (IEC 62439-3:2012)."
+                  format="choice (NMSettingHsrProtocolVersion)"
+                  values="default (-1), hsr-2010 (0), hsr-2012 (1)" />
+        <property name="interlink"
+                  nmcli-description="The optional interlink port name of the HSR interface."
+                  format="string" />
     </setting>
     <setting name="infiniband" >
         <property name="mac-address"
diff --git a/src/nmcli/general.c b/src/nmcli/general.c
index 2e9aad44..a2772ca3 100644
--- a/src/nmcli/general.c
+++ b/src/nmcli/general.c
@@ -28,7 +28,7 @@ static void permission_changed(GObject *gobject, GParamSpec *pspec, NmCli *nmc);
 static NM_UTILS_LOOKUP_STR_DEFINE(nm_state_to_string,
                                   NMState,
                                   NM_UTILS_LOOKUP_DEFAULT(N_("unknown")),
-                                  NM_UTILS_LOOKUP_ITEM(NM_STATE_ASLEEP, N_("asleep")),
+                                  NM_UTILS_LOOKUP_ITEM(NM_STATE_DISABLED, N_("network off")),
                                   NM_UTILS_LOOKUP_ITEM(NM_STATE_CONNECTING, N_("connecting")),
                                   NM_UTILS_LOOKUP_ITEM(NM_STATE_CONNECTED_LOCAL,
                                                        N_("connected (local only)")),
@@ -53,8 +53,8 @@ state_to_color(NMState state)
         return NM_META_COLOR_STATE_CONNECTED_GLOBAL;
     case NM_STATE_DISCONNECTING:
         return NM_META_COLOR_STATE_DISCONNECTING;
-    case NM_STATE_ASLEEP:
-        return NM_META_COLOR_STATE_ASLEEP;
+    case NM_STATE_DISABLED:
+        return NM_META_COLOR_STATE_DISABLED;
     case NM_STATE_DISCONNECTED:
         return NM_META_COLOR_STATE_DISCONNECTED;
     default:
diff --git a/src/nmcli/nmcli.c b/src/nmcli/nmcli.c
index cfe4c5f4..09e15db9 100644
--- a/src/nmcli/nmcli.c
+++ b/src/nmcli/nmcli.c
@@ -581,6 +581,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"permission-unknown", NM_META_COLOR_PERMISSION_UNKNOWN},
     {"permission-yes", NM_META_COLOR_PERMISSION_YES},
     {"prompt", NM_META_COLOR_PROMPT},
+    {"state-disabled", NM_META_COLOR_STATE_DISABLED},
     {"state-asleep", NM_META_COLOR_STATE_ASLEEP},
     {"state-connected-global", NM_META_COLOR_STATE_CONNECTED_GLOBAL},
     {"state-connected-local", NM_META_COLOR_STATE_CONNECTED_LOCAL},