summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/c-stdaux/src/c-stdaux-generic.h273
-rw-r--r--src/core/NetworkManagerUtils.c171
-rw-r--r--src/core/NetworkManagerUtils.h34
-rw-r--r--src/core/devices/adsl/nm-device-adsl.c7
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.c7
-rw-r--r--src/core/devices/nm-device-6lowpan.c14
-rw-r--r--src/core/devices/nm-device-bond.c62
-rw-r--r--src/core/devices/nm-device-bridge.c235
-rw-r--r--src/core/devices/nm-device-ethernet.c7
-rw-r--r--src/core/devices/nm-device-factory.h3
-rw-r--r--src/core/devices/nm-device-generic.c46
-rw-r--r--src/core/devices/nm-device-infiniband.c11
-rw-r--r--src/core/devices/nm-device-ip-tunnel.c9
-rw-r--r--src/core/devices/nm-device-logging.h52
-rw-r--r--src/core/devices/nm-device-macvlan.c23
-rw-r--r--src/core/devices/nm-device-ppp.c7
-rw-r--r--src/core/devices/nm-device-private.h6
-rw-r--r--src/core/devices/nm-device-tun.c9
-rw-r--r--src/core/devices/nm-device-vlan.c9
-rw-r--r--src/core/devices/nm-device-vrf.c20
-rw-r--r--src/core/devices/nm-device-vxlan.c9
-rw-r--r--src/core/devices/nm-device-wpan.c7
-rw-r--r--src/core/devices/nm-device.c818
-rw-r--r--src/core/devices/nm-device.h43
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c13
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c13
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c58
-rw-r--r--src/core/devices/ovs/nm-ovs-factory.c6
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c335
-rw-r--r--src/core/devices/team/nm-device-team.c11
-rw-r--r--src/core/devices/wifi/nm-device-iwd-p2p.c7
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c36
-rw-r--r--src/core/devices/wifi/nm-device-olpc-mesh.c2
-rw-r--r--src/core/devices/wifi/nm-device-wifi-p2p.c7
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c15
-rw-r--r--src/core/devices/wwan/nm-device-modem.c7
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.c83
-rw-r--r--src/core/devices/wwan/nm-modem.c31
-rw-r--r--src/core/devices/wwan/nm-modem.h2
-rw-r--r--src/core/dhcp/nm-dhcp-client-logging.h7
-rw-r--r--src/core/dhcp/nm-dhcp-client.c10
-rw-r--r--src/core/dhcp/nm-dhcp-client.h9
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient.c12
-rw-r--r--src/core/dhcp/nm-dhcp-helper.c31
-rw-r--r--src/core/dhcp/nm-dhcp-options.c2
-rw-r--r--src/core/dhcp/nm-dhcp-options.h3
-rw-r--r--src/core/dhcp/nm-dhcp-systemd.c29
-rw-r--r--src/core/dns/nm-dns-dnsmasq.c1
-rw-r--r--src/core/dns/nm-dns-manager.c90
-rw-r--r--src/core/main.c16
-rw-r--r--src/core/nm-act-request.c4
-rw-r--r--src/core/nm-active-connection.c2
-rw-r--r--src/core/nm-auth-utils.c2
-rw-r--r--src/core/nm-bond-manager.c8
-rw-r--r--src/core/nm-checkpoint.c39
-rw-r--r--src/core/nm-config-data.c188
-rw-r--r--src/core/nm-config-data.h47
-rw-r--r--src/core/nm-config.c6
-rw-r--r--src/core/nm-config.h2
-rw-r--r--src/core/nm-connectivity.c18
-rw-r--r--src/core/nm-core-utils.c350
-rw-r--r--src/core/nm-core-utils.h45
-rw-r--r--src/core/nm-dbus-manager.c81
-rw-r--r--src/core/nm-dbus-manager.h4
-rw-r--r--src/core/nm-firewall-utils.c51
-rw-r--r--src/core/nm-firewall-utils.h3
-rw-r--r--src/core/nm-keep-alive.c2
-rw-r--r--src/core/nm-l3-config-data.c29
-rw-r--r--src/core/nm-l3-config-data.h2
-rw-r--r--src/core/nm-l3-ipv6ll.c12
-rw-r--r--src/core/nm-l3cfg.c757
-rw-r--r--src/core/nm-l3cfg.h8
-rw-r--r--src/core/nm-manager.c916
-rw-r--r--src/core/nm-manager.h47
-rw-r--r--src/core/nm-netns.c597
-rw-r--r--src/core/nm-netns.h42
-rw-r--r--src/core/nm-policy.c533
-rw-r--r--src/core/nm-policy.h2
-rw-r--r--src/core/nm-test-utils-core.h4
-rw-r--r--src/core/platform/nm-fake-platform.c99
-rw-r--r--src/core/platform/tests/test-cleanup.c4
-rw-r--r--src/core/platform/tests/test-common.c6
-rw-r--r--src/core/platform/tests/test-link.c252
-rw-r--r--src/core/platform/tests/test-route.c9
-rw-r--r--src/core/settings/nm-secret-agent.c2
-rw-r--r--src/core/settings/nm-settings-connection.c321
-rw-r--r--src/core/settings/nm-settings-connection.h59
-rw-r--r--src/core/settings/nm-settings.c137
-rw-r--r--src/core/settings/nm-settings.h2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c16
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c28
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c26
-rw-r--r--src/core/settings/plugins/ifcfg-rh/shvar.c23
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.c60
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.c31
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-writer.h18
-rw-r--r--src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c16
-rw-r--r--src/core/supplicant/nm-supplicant-manager.c6
-rw-r--r--src/core/tests/test-core-with-expect.c10
-rw-r--r--src/core/tests/test-core.c147
-rw-r--r--src/libnm-base/nm-base.c34
-rw-r--r--src/libnm-base/nm-base.h5
-rw-r--r--src/libnm-base/nm-config-base.h2
-rw-r--r--src/libnm-client-impl/libnm.ver15
-rw-r--r--src/libnm-client-impl/meson.build1
-rw-r--r--src/libnm-client-impl/nm-active-connection.c13
-rw-r--r--src/libnm-client-impl/nm-client.c88
-rw-r--r--src/libnm-client-impl/nm-conn-utils.c16
-rw-r--r--src/libnm-client-impl/nm-device-bond.c2
-rw-r--r--src/libnm-client-impl/nm-device-bridge.c2
-rw-r--r--src/libnm-client-impl/nm-device-ovs-bridge.c2
-rw-r--r--src/libnm-client-impl/nm-device-ovs-port.c2
-rw-r--r--src/libnm-client-impl/nm-device-team.c2
-rw-r--r--src/libnm-client-impl/nm-device-wifi-p2p.c2
-rw-r--r--src/libnm-client-impl/nm-device.c13
-rw-r--r--src/libnm-client-impl/nm-remote-connection.c50
-rw-r--r--src/libnm-client-public/NetworkManager.h5
-rw-r--r--src/libnm-client-public/nm-active-connection.h3
-rw-r--r--src/libnm-client-public/nm-autoptr.h9
-rw-r--r--src/libnm-client-public/nm-remote-connection.h4
-rw-r--r--src/libnm-client-test/nm-test-utils-impl.c3
-rw-r--r--src/libnm-core-aux-intern/nm-libnm-core-utils.c21
-rw-r--r--src/libnm-core-aux-intern/nm-libnm-core-utils.h21
-rw-r--r--src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in35
-rw-r--r--src/libnm-core-impl/meson.build1
-rw-r--r--src/libnm-core-impl/nm-connection.c230
-rw-r--r--src/libnm-core-impl/nm-keyfile.c25
-rw-r--r--src/libnm-core-impl/nm-meta-setting-base-impl.c9
-rw-r--r--src/libnm-core-impl/nm-setting-8021x.c4
-rw-r--r--src/libnm-core-impl/nm-setting-bond-port.c4
-rw-r--r--src/libnm-core-impl/nm-setting-bond.c86
-rw-r--r--src/libnm-core-impl/nm-setting-connection.c65
-rw-r--r--src/libnm-core-impl/nm-setting-ethtool.c8
-rw-r--r--src/libnm-core-impl/nm-setting-gsm.c186
-rw-r--r--src/libnm-core-impl/nm-setting-infiniband.c44
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c58
-rw-r--r--src/libnm-core-impl/nm-setting-ip4-config.c58
-rw-r--r--src/libnm-core-impl/nm-setting-ip6-config.c274
-rw-r--r--src/libnm-core-impl/nm-setting-link.c241
-rw-r--r--src/libnm-core-impl/nm-setting-match.c8
-rw-r--r--src/libnm-core-impl/nm-setting-ovs-external-ids.c6
-rw-r--r--src/libnm-core-impl/nm-setting-ovs-other-config.c2
-rw-r--r--src/libnm-core-impl/nm-setting-sriov.c8
-rw-r--r--src/libnm-core-impl/nm-setting-tc-config.c15
-rw-r--r--src/libnm-core-impl/nm-setting-team.c6
-rw-r--r--src/libnm-core-impl/nm-setting-user.c4
-rw-r--r--src/libnm-core-impl/nm-setting-vlan.c4
-rw-r--r--src/libnm-core-impl/nm-setting-vpn.c12
-rw-r--r--src/libnm-core-impl/nm-setting-wired.c13
-rw-r--r--src/libnm-core-impl/nm-setting-wireguard.c20
-rw-r--r--src/libnm-core-impl/nm-setting-wireless.c61
-rw-r--r--src/libnm-core-impl/nm-setting.c22
-rw-r--r--src/libnm-core-impl/nm-utils.c71
-rw-r--r--src/libnm-core-impl/nm-vpn-editor-plugin.c2
-rw-r--r--src/libnm-core-impl/nm-vpn-plugin-info.c12
-rw-r--r--src/libnm-core-impl/tests/test-general.c99
-rw-r--r--src/libnm-core-impl/tests/test-setting.c3
-rw-r--r--src/libnm-core-intern/nm-core-internal.h20
-rw-r--r--src/libnm-core-intern/nm-meta-setting-base-impl.h1
-rw-r--r--src/libnm-core-public/meson.build1
-rw-r--r--src/libnm-core-public/nm-core-types.h1
-rw-r--r--src/libnm-core-public/nm-dbus-interface.h4
-rw-r--r--src/libnm-core-public/nm-dbus-types.xml4
-rw-r--r--src/libnm-core-public/nm-errors.h24
-rw-r--r--src/libnm-core-public/nm-setting-bond-port.h2
-rw-r--r--src/libnm-core-public/nm-setting-bond.h3
-rw-r--r--src/libnm-core-public/nm-setting-connection.h4
-rw-r--r--src/libnm-core-public/nm-setting-gsm.h32
-rw-r--r--src/libnm-core-public/nm-setting-ip-config.h2
-rw-r--r--src/libnm-core-public/nm-setting-ip6-config.h4
-rw-r--r--src/libnm-core-public/nm-setting-link.h52
-rw-r--r--src/libnm-core-public/nm-setting-wireless.h9
-rw-r--r--src/libnm-core-public/nm-setting.h3
-rw-r--r--src/libnm-core-public/nm-version-macros.h7
-rw-r--r--src/libnm-core-public/nm-version-macros.h.in3
-rw-r--r--src/libnm-core-public/nm-version.h19
-rw-r--r--src/libnm-crypto/nm-crypto.c6
-rw-r--r--src/libnm-glib-aux/nm-dbus-aux.c12
-rw-r--r--src/libnm-glib-aux/nm-dbus-aux.h2
-rw-r--r--src/libnm-glib-aux/nm-dedup-multi.c6
-rw-r--r--src/libnm-glib-aux/nm-glib.h2
-rw-r--r--src/libnm-glib-aux/nm-hash-utils.h8
-rw-r--r--src/libnm-glib-aux/nm-inet-utils.c151
-rw-r--r--src/libnm-glib-aux/nm-inet-utils.h75
-rw-r--r--src/libnm-glib-aux/nm-io-utils.c100
-rw-r--r--src/libnm-glib-aux/nm-io-utils.h10
-rw-r--r--src/libnm-glib-aux/nm-logging-fwd.h18
-rw-r--r--src/libnm-glib-aux/nm-macros-internal.h212
-rw-r--r--src/libnm-glib-aux/nm-prioq.c192
-rw-r--r--src/libnm-glib-aux/nm-prioq.h4
-rw-r--r--src/libnm-glib-aux/nm-random-utils.c2
-rw-r--r--src/libnm-glib-aux/nm-ref-string.h2
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.c363
-rw-r--r--src/libnm-glib-aux/nm-shared-utils.h56
-rw-r--r--src/libnm-glib-aux/nm-str-buf.h4
-rw-r--r--src/libnm-glib-aux/nm-test-utils.h53
-rw-r--r--src/libnm-glib-aux/nm-uuid.c2
-rw-r--r--src/libnm-glib-aux/tests/test-shared-general.c180
-rw-r--r--src/libnm-lldp/nm-lldp-neighbor.c1
-rw-r--r--src/libnm-log-core/nm-logging.c27
-rw-r--r--src/libnm-platform/nm-linux-platform.c596
-rw-r--r--src/libnm-platform/nm-netlink.c94
-rw-r--r--src/libnm-platform/nm-netlink.h63
-rw-r--r--src/libnm-platform/nm-platform-utils.c35
-rw-r--r--src/libnm-platform/nm-platform-utils.h3
-rw-r--r--src/libnm-platform/nm-platform.c482
-rw-r--r--src/libnm-platform/nm-platform.h201
-rw-r--r--src/libnm-platform/nmp-base.h15
-rw-r--r--src/libnm-platform/nmp-global-tracker.c7
-rw-r--r--src/libnm-platform/nmp-object.c20
-rw-r--r--src/libnm-platform/nmp-object.h15
-rw-r--r--src/libnm-platform/nmp-plobj.c4
-rw-r--r--src/libnm-platform/nmp-plobj.h3
-rw-r--r--src/libnm-platform/tests/test-nm-platform.c1
-rw-r--r--src/libnm-std-aux/c-list-util.h6
-rw-r--r--src/libnm-std-aux/nm-std-aux.h15
-rw-r--r--src/libnm-std-aux/nm-std-utils.c4
-rw-r--r--src/libnm-std-aux/unaligned-fundamental.h40
-rw-r--r--src/libnm-std-aux/unaligned.h20
-rw-r--r--src/libnm-systemd-core/meson.build1
-rw-r--r--src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c15
-rw-r--r--src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h10
-rw-r--r--src/libnm-systemd-core/sd-adapt-core/sd-messages.h (renamed from src/libnm-systemd-core/sd-adapt-core/device-util.h)0
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c38
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h4
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h5
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c2
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c25
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c1
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h1
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c107
-rw-r--r--src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c6
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h103
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h12
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c4
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c921
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c145
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h44
-rw-r--r--src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c118
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-device.h167
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h3
-rw-r--r--src/libnm-systemd-core/src/systemd/sd-event.h6
-rw-r--r--src/libnm-systemd-shared/meson.build2
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/argv-util.h (renamed from src/libnm-systemd-core/sd-adapt-core/sd-device.h)0
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/chase.h (renamed from src/libnm-systemd-shared/sd-adapt-shared/def.h)0
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h10
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/psi-util.h3
-rw-r--r--src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h3
-rw-r--r--src/libnm-systemd-shared/src/basic/alloc-util.c4
-rw-r--r--src/libnm-systemd-shared/src/basic/alloc-util.h40
-rw-r--r--src/libnm-systemd-shared/src/basic/cgroup-util.h7
-rw-r--r--src/libnm-systemd-shared/src/basic/constants.h112
-rw-r--r--src/libnm-systemd-shared/src/basic/env-file.c109
-rw-r--r--src/libnm-systemd-shared/src/basic/env-file.h14
-rw-r--r--src/libnm-systemd-shared/src/basic/env-util.c117
-rw-r--r--src/libnm-systemd-shared/src/basic/env-util.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/escape.c56
-rw-r--r--src/libnm-systemd-shared/src/basic/fd-util.c253
-rw-r--r--src/libnm-systemd-shared/src/basic/fd-util.h47
-rw-r--r--src/libnm-systemd-shared/src/basic/fileio.c285
-rw-r--r--src/libnm-systemd-shared/src/basic/fileio.h48
-rw-r--r--src/libnm-systemd-shared/src/basic/fs-util.c260
-rw-r--r--src/libnm-systemd-shared/src/basic/fs-util.h23
-rw-r--r--src/libnm-systemd-shared/src/basic/glyph-util.c22
-rw-r--r--src/libnm-systemd-shared/src/basic/glyph-util.h1
-rw-r--r--src/libnm-systemd-shared/src/basic/hash-funcs.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/hashmap.c52
-rw-r--r--src/libnm-systemd-shared/src/basic/hashmap.h3
-rw-r--r--src/libnm-systemd-shared/src/basic/hexdecoct.c229
-rw-r--r--src/libnm-systemd-shared/src/basic/hexdecoct.h10
-rw-r--r--src/libnm-systemd-shared/src/basic/hostname-util.c4
-rw-r--r--src/libnm-systemd-shared/src/basic/hostname-util.h8
-rw-r--r--src/libnm-systemd-shared/src/basic/in-addr-util.c34
-rw-r--r--src/libnm-systemd-shared/src/basic/in-addr-util.h5
-rw-r--r--src/libnm-systemd-shared/src/basic/io-util.c28
-rw-r--r--src/libnm-systemd-shared/src/basic/io-util.h22
-rw-r--r--src/libnm-systemd-shared/src/basic/label.c32
-rw-r--r--src/libnm-systemd-shared/src/basic/label.h14
-rw-r--r--src/libnm-systemd-shared/src/basic/list.h78
-rw-r--r--src/libnm-systemd-shared/src/basic/locale-util.c14
-rw-r--r--src/libnm-systemd-shared/src/basic/lock-util.h42
-rw-r--r--src/libnm-systemd-shared/src/basic/log.h149
-rw-r--r--src/libnm-systemd-shared/src/basic/macro.h80
-rw-r--r--src/libnm-systemd-shared/src/basic/memory-util.c19
-rw-r--r--src/libnm-systemd-shared/src/basic/memory-util.h46
-rw-r--r--src/libnm-systemd-shared/src/basic/mempool.c124
-rw-r--r--src/libnm-systemd-shared/src/basic/mempool.h8
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_fcntl.h17
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_syscall.h124
-rw-r--r--src/libnm-systemd-shared/src/basic/missing_threads.h15
-rw-r--r--src/libnm-systemd-shared/src/basic/origin-id.h36
-rw-r--r--src/libnm-systemd-shared/src/basic/parse-util.c39
-rw-r--r--src/libnm-systemd-shared/src/basic/parse-util.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/path-util.c127
-rw-r--r--src/libnm-systemd-shared/src/basic/path-util.h14
-rw-r--r--src/libnm-systemd-shared/src/basic/prioq.c5
-rw-r--r--src/libnm-systemd-shared/src/basic/prioq.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/process-util.c402
-rw-r--r--src/libnm-systemd-shared/src/basic/process-util.h46
-rw-r--r--src/libnm-systemd-shared/src/basic/random-util.c9
-rw-r--r--src/libnm-systemd-shared/src/basic/random-util.h1
-rw-r--r--src/libnm-systemd-shared/src/basic/ratelimit.c41
-rw-r--r--src/libnm-systemd-shared/src/basic/ratelimit.h3
-rw-r--r--src/libnm-systemd-shared/src/basic/signal-util.c19
-rw-r--r--src/libnm-systemd-shared/src/basic/signal-util.h2
-rw-r--r--src/libnm-systemd-shared/src/basic/socket-util.c190
-rw-r--r--src/libnm-systemd-shared/src/basic/socket-util.h41
-rw-r--r--src/libnm-systemd-shared/src/basic/stat-util.c137
-rw-r--r--src/libnm-systemd-shared/src/basic/stat-util.h26
-rw-r--r--src/libnm-systemd-shared/src/basic/stdio-util.h23
-rw-r--r--src/libnm-systemd-shared/src/basic/string-table.h1
-rw-r--r--src/libnm-systemd-shared/src/basic/string-util.c119
-rw-r--r--src/libnm-systemd-shared/src/basic/string-util.h47
-rw-r--r--src/libnm-systemd-shared/src/basic/strv.c161
-rw-r--r--src/libnm-systemd-shared/src/basic/strv.h45
-rw-r--r--src/libnm-systemd-shared/src/basic/time-util.c487
-rw-r--r--src/libnm-systemd-shared/src/basic/time-util.h45
-rw-r--r--src/libnm-systemd-shared/src/basic/tmpfile-util.c198
-rw-r--r--src/libnm-systemd-shared/src/basic/tmpfile-util.h32
-rw-r--r--src/libnm-systemd-shared/src/basic/umask-util.h6
-rw-r--r--src/libnm-systemd-shared/src/basic/user-util.h13
-rw-r--r--src/libnm-systemd-shared/src/basic/util.c178
-rw-r--r--src/libnm-systemd-shared/src/fundamental/logarithm.h (renamed from src/libnm-systemd-shared/src/basic/util.h)37
-rw-r--r--src/libnm-systemd-shared/src/fundamental/macro-fundamental.h140
-rw-r--r--src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h66
-rw-r--r--src/libnm-systemd-shared/src/fundamental/sha256.c28
-rw-r--r--src/libnm-systemd-shared/src/fundamental/sha256.h8
-rw-r--r--src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c4
-rw-r--r--src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h13
-rw-r--r--src/libnm-systemd-shared/src/shared/dns-domain.c32
-rw-r--r--src/libnm-systemd-shared/src/shared/dns-domain.h5
-rw-r--r--src/libnmc-base/nm-polkit-listener.c10
-rw-r--r--src/libnmc-base/nm-secret-agent-simple.c6
-rw-r--r--src/libnmc-base/nm-vpn-helpers.c246
-rw-r--r--src/libnmc-base/nm-vpn-helpers.h8
-rw-r--r--src/libnmc-setting/nm-meta-setting-base-impl.c9
-rw-r--r--src/libnmc-setting/nm-meta-setting-base-impl.h1
-rw-r--r--src/libnmc-setting/nm-meta-setting-desc.c92
-rw-r--r--src/libnmc-setting/settings-docs.h33
-rw-r--r--src/libnmc-setting/settings-docs.h.in33
-rw-r--r--src/libnmt-newt/nmt-newt-form.c4
-rw-r--r--src/libnmt-newt/nmt-newt-listbox.c2
-rw-r--r--src/libnmt-newt/nmt-newt-widget.c2
-rw-r--r--src/n-dhcp4/src/n-dhcp4-c-connection.c16
-rw-r--r--src/n-dhcp4/src/n-dhcp4-private.h1
-rwxr-xr-xsrc/nm-cloud-setup/90-nm-cloud-setup.sh16
-rw-r--r--src/nm-cloud-setup/main.c183
-rw-r--r--src/nm-cloud-setup/meson.build2
-rw-r--r--src/nm-cloud-setup/nm-cloud-setup-utils.c252
-rw-r--r--src/nm-cloud-setup/nm-cloud-setup-utils.h73
-rw-r--r--src/nm-cloud-setup/nm-cloud-setup.service.in1
-rw-r--r--src/nm-cloud-setup/nm-http-client.c34
-rw-r--r--src/nm-cloud-setup/nmcs-provider-aliyun.c24
-rw-r--r--src/nm-cloud-setup/nmcs-provider-azure.c6
-rw-r--r--src/nm-cloud-setup/nmcs-provider-ec2.c24
-rw-r--r--src/nm-cloud-setup/nmcs-provider-gcp.c29
-rw-r--r--src/nm-cloud-setup/nmcs-provider.c4
-rw-r--r--src/nm-cloud-setup/nmcs-provider.h7
-rw-r--r--src/nm-dispatcher/nm-dispatcher.c2
-rw-r--r--src/nm-initrd-generator/nmi-cmdline-reader.c34
-rw-r--r--src/nm-initrd-generator/nmi-dt-reader.c5
-rw-r--r--src/nm-initrd-generator/tests/test-cmdline-reader.c81
-rw-r--r--src/nmcli/common.c57
-rw-r--r--src/nmcli/connections.c367
-rw-r--r--src/nmcli/devices.c56
-rw-r--r--src/nmcli/gen-metadata-nm-settings-nmcli.c16
-rw-r--r--src/nmcli/gen-metadata-nm-settings-nmcli.xml.in900
-rw-r--r--src/nmcli/general.c4
-rw-r--r--src/nmcli/nmcli.c2
-rw-r--r--src/nmcli/nmcli.h6
-rw-r--r--src/nmcli/settings.c9
-rw-r--r--src/nmcli/utils.c21
-rw-r--r--src/nmcli/utils.h6
-rw-r--r--src/nmtui/meson.build3
-rw-r--r--src/nmtui/nm-editor-utils.c70
-rw-r--r--src/nmtui/nm-editor-utils.h5
-rw-r--r--src/nmtui/nmt-edit-connection-list.c2
-rw-r--r--src/nmtui/nmt-editor-grid.c6
-rw-r--r--src/nmtui/nmt-editor-page.c2
-rw-r--r--src/nmtui/nmt-editor-section.c2
-rw-r--r--src/nmtui/nmt-editor.c12
-rw-r--r--src/nmtui/nmt-page-bond.c48
-rw-r--r--src/nmtui/nmt-page-bridge.c10
-rw-r--r--src/nmtui/nmt-page-team.c42
-rw-r--r--src/nmtui/nmt-port-list.c254
-rw-r--r--src/nmtui/nmt-port-list.h37
-rw-r--r--src/nmtui/nmt-slave-list.c253
-rw-r--r--src/nmtui/nmt-slave-list.h38
-rw-r--r--src/nmtui/nmt-utils.c4
-rw-r--r--src/nmtui/nmt-widget-list.c2
-rw-r--r--src/nmtui/nmtui-connect.c110
-rw-r--r--src/nmtui/nmtui-edit.c72
-rw-r--r--src/nmtui/nmtui-edit.h2
-rw-r--r--src/nmtui/nmtui-radio.c130
-rw-r--r--src/nmtui/nmtui-radio.h11
-rw-r--r--src/nmtui/nmtui.c4
-rw-r--r--src/tests/client/meson.build19
-rw-r--r--src/tests/client/test-client.check-on-disk/test_002.expected10
-rw-r--r--src/tests/client/test-client.check-on-disk/test_003.expected652
-rw-r--r--src/tests/client/test-client.check-on-disk/test_004.expected800
-rw-r--r--src/tests/client/test-client.check-on-disk/test_version_warn.expected30
-rwxr-xr-xsrc/tests/client/test-client.py1242
-rwxr-xr-xsrc/tests/client/test-client.sh5
408 files changed, 18093 insertions, 8734 deletions
diff --git a/src/c-stdaux/src/c-stdaux-generic.h b/src/c-stdaux/src/c-stdaux-generic.h
index 4848617a..6f87fd4a 100644
--- a/src/c-stdaux/src/c-stdaux-generic.h
+++ b/src/c-stdaux/src/c-stdaux-generic.h
@@ -290,6 +290,27 @@ extern "C" {
 /**/
 
 /**
+ * c_assume_aligned() - Hint alignment to compiler
+ * @_ptr:               Pointer to provide alignment hint for
+ * @_alignment:         Alignment in bytes
+ * @_offset:            Misalignment offset
+ *
+ * This hints to the compiler that `_ptr - _offset` is aligned to the alignment
+ * specified in `_alignment`.
+ *
+ * On platforms without support for `__builtin_assume_aligned()` this is a
+ * no-op.
+ *
+ * Return: `_ptr` is returned.
+ */
+#define c_assume_aligned(_ptr, _alignment, _offset) c_internal_assume_aligned((_ptr), (_alignment), (_offset))
+#if (defined(C_COMPILER_GNUC) && __GNUC__ > 5) || (defined(C_COMPILER_CLANG) && __clang_major__ > 3)
+#  define c_internal_assume_aligned(_ptr, _alignment, _offset) __builtin_assume_aligned((_ptr), (_alignment), (_offset))
+#else
+#  define c_internal_assume_aligned(_ptr, _alignment, _offset) ((void)(_alignment), (void)(_offset), (_ptr))
+#endif
+
+/**
  * c_assert() - Runtime assertions
  * @_x:                 Result of an expression
  *
@@ -401,6 +422,258 @@ static inline int c_memcmp(const void *s1, const void *s2, size_t n) {
 }
 
 /**
+ * DOC: Memory Access
+ *
+ * This section provides helpers to read and write arbitrary memory locations.
+ * They are carefully designed to follow all language restrictions and thus
+ * work with strict-aliasing and alignment rules.
+ *
+ * The C language does not allow aliasing an object with a pointer of an
+ * incompatible type (with few exceptions). Furthermore, memory access must be
+ * aligned. This function uses exceptions in the language to circumvent both
+ * restrictions.
+ *
+ * Note that pointer-offset calculations should avoid exceeding the extents of
+ * the object, even if the object is surrounded by other objects. That is,
+ * `ptr+offset` should point to the same object as `ptr`. Otherwise, pointer
+ * provenance will have to be considered.
+ */
+/**/
+
+/**
+ * c_load_8() - Read a u8 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an unsigned 8-bit integer at the offset of the specified memory
+ * location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint8_t c_load_8(const void *memory, size_t offset) {
+        return ((const uint8_t *)memory)[offset];
+}
+
+/**
+ * c_load_16be_unaligned() - Read an unaligned big-endian u16 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an unaligned big-endian unsigned 16-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint16_t c_load_16be_unaligned(const void *memory, size_t offset) {
+        const uint8_t *m = (const uint8_t *)memory + offset;
+        return ((uint16_t)m[1] << 0) | ((uint16_t)m[0] << 8);
+}
+
+/**
+ * c_load_16be_aligned() - Read an aligned big-endian u16 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an aligned big-endian unsigned 16-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint16_t c_load_16be_aligned(const void *memory, size_t offset) {
+        const uint8_t *m = c_assume_aligned((const uint8_t *)memory + offset, 2, 0);
+        return ((uint16_t)m[1] << 0) | ((uint16_t)m[0] << 8);
+}
+
+/**
+ * c_load_16le_unaligned() - Read an unaligned little-endian u16 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an unaligned little-endian unsigned 16-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint16_t c_load_16le_unaligned(const void *memory, size_t offset) {
+        const uint8_t *m = (const uint8_t *)memory + offset;
+        return ((uint16_t)m[0] << 0) | ((uint16_t)m[1] << 8);
+}
+
+/**
+ * c_load_16le_aligned() - Read an aligned little-endian u16 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an aligned little-endian unsigned 16-bit integer at the offset of
+ * the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint16_t c_load_16le_aligned(const void *memory, size_t offset) {
+        const uint8_t *m = c_assume_aligned((const uint8_t *)memory + offset, 2, 0);
+        return ((uint16_t)m[0] << 0) | ((uint16_t)m[1] << 8);
+}
+
+/**
+ * c_load_32be_unaligned() - Read an unaligned big-endian u32 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an unaligned big-endian unsigned 32-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint32_t c_load_32be_unaligned(const void *memory, size_t offset) {
+        const uint8_t *m = (const uint8_t *)memory + offset;
+        return ((uint32_t)m[3] <<  0) | ((uint32_t)m[2] <<  8) |
+               ((uint32_t)m[1] << 16) | ((uint32_t)m[0] << 24);
+}
+
+/**
+ * c_load_32be_aligned() - Read an aligned big-endian u32 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an aligned big-endian unsigned 32-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint32_t c_load_32be_aligned(const void *memory, size_t offset) {
+        const uint8_t *m = c_assume_aligned((const uint8_t *)memory + offset, 4, 0);
+        return ((uint32_t)m[3] <<  0) | ((uint32_t)m[2] <<  8) |
+               ((uint32_t)m[1] << 16) | ((uint32_t)m[0] << 24);
+}
+
+/**
+ * c_load_32le_unaligned() - Read an unaligned little-endian u32 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an unaligned little-endian unsigned 32-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint32_t c_load_32le_unaligned(const void *memory, size_t offset) {
+        const uint8_t *m = (const uint8_t *)memory + offset;
+        return ((uint32_t)m[0] <<  0) | ((uint32_t)m[1] <<  8) |
+               ((uint32_t)m[2] << 16) | ((uint32_t)m[3] << 24);
+}
+
+/**
+ * c_load_32le_aligned() - Read an aligned little-endian u32 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an aligned little-endian unsigned 32-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint32_t c_load_32le_aligned(const void *memory, size_t offset) {
+        const uint8_t *m = c_assume_aligned((const uint8_t *)memory + offset, 4, 0);
+        return ((uint32_t)m[0] <<  0) | ((uint32_t)m[1] <<  8) |
+               ((uint32_t)m[2] << 16) | ((uint32_t)m[3] << 24);
+}
+
+/**
+ * c_load_64be_unaligned() - Read an unaligned big-endian u64 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an unaligned big-endian unsigned 64-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint64_t c_load_64be_unaligned(const void *memory, size_t offset) {
+        const uint8_t *m = (const uint8_t *)memory + offset;
+        return ((uint64_t)m[7] <<  0) | ((uint64_t)m[6] <<  8) |
+               ((uint64_t)m[5] << 16) | ((uint64_t)m[4] << 24) |
+               ((uint64_t)m[3] << 32) | ((uint64_t)m[2] << 40) |
+               ((uint64_t)m[1] << 48) | ((uint64_t)m[0] << 56);
+}
+
+/**
+ * c_load_64be_aligned() - Read an aligned big-endian u64 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an aligned big-endian unsigned 64-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint64_t c_load_64be_aligned(const void *memory, size_t offset) {
+        const uint8_t *m = c_assume_aligned((const uint8_t *)memory + offset, 8, 0);
+        return ((uint64_t)m[7] <<  0) | ((uint64_t)m[6] <<  8) |
+               ((uint64_t)m[5] << 16) | ((uint64_t)m[4] << 24) |
+               ((uint64_t)m[3] << 32) | ((uint64_t)m[2] << 40) |
+               ((uint64_t)m[1] << 48) | ((uint64_t)m[0] << 56);
+}
+
+/**
+ * c_load_64le_unaligned() - Read an unaligned little-endian u64 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an unaligned little-endian unsigned 64-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint64_t c_load_64le_unaligned(const void *memory, size_t offset) {
+        const uint8_t *m = (const uint8_t *)memory + offset;
+        return ((uint64_t)m[0] <<  0) | ((uint64_t)m[1] <<  8) |
+               ((uint64_t)m[2] << 16) | ((uint64_t)m[3] << 24) |
+               ((uint64_t)m[4] << 32) | ((uint64_t)m[5] << 40) |
+               ((uint64_t)m[6] << 48) | ((uint64_t)m[7] << 56);
+}
+
+/**
+ * c_load_64le_aligned() - Read an aligned little-endian u64 from memory
+ * @memory:     Memory location to operate on
+ * @offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads an aligned little-endian unsigned 64-bit integer at the offset
+ * of the specified memory location.
+ *
+ * Return: The read value is returned.
+ */
+static inline uint64_t c_load_64le_aligned(const void *memory, size_t offset) {
+        const uint8_t *m = c_assume_aligned((const uint8_t *)memory + offset, 8, 0);
+        return ((uint64_t)m[0] <<  0) | ((uint64_t)m[1] <<  8) |
+               ((uint64_t)m[2] << 16) | ((uint64_t)m[3] << 24) |
+               ((uint64_t)m[4] << 32) | ((uint64_t)m[5] << 40) |
+               ((uint64_t)m[6] << 48) | ((uint64_t)m[7] << 56);
+}
+
+/**
+ * c_load() - Read from memory
+ * @_type:      Datatype to read
+ * @_endian:    Endianness
+ * @_aligned:   Aligned or unaligned access
+ * @_memory:     Memory location to operate on
+ * @_offset:     Offset in bytes from the pointed memory location
+ *
+ * This reads a value of the same size as `_type` at the offset of the
+ * specified memory location. `_endian` must be either `be` or `le`, `_aligned`
+ * must be either `aligned` or `unaligned`.
+ *
+ * This is a generic macro that maps to the respective `c_load_*()` function.
+ *
+ * Return: The read value is returned.
+ */
+#define c_load(_type, _endian, _aligned, _memory, _offset)                              \
+        (_Generic((_type){ 0 },                                                         \
+                uint16_t: c_load_16 ## _endian ## _ ## _aligned ((_memory), (_offset)), \
+                uint32_t: c_load_32 ## _endian ## _ ## _aligned ((_memory), (_offset)), \
+                uint64_t: c_load_64 ## _endian ## _ ## _aligned ((_memory), (_offset))  \
+        ))
+
+/**
  * DOC: Generic Destructors
  *
  * A set of destructors is provided which extends standard library destructors
diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c
index 6f4c60f8..8606082c 100644
--- a/src/core/NetworkManagerUtils.c
+++ b/src/core/NetworkManagerUtils.c
@@ -23,6 +23,7 @@
 #include "nm-setting-connection.h"
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-ip6-config.h"
+#include "settings/nm-settings.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-platform/nmp-object.h"
 
@@ -30,6 +31,7 @@
 #include "libnm-platform/nm-linux-platform.h"
 #include "libnm-platform/nm-platform-utils.h"
 #include "nm-auth-utils.h"
+#include "devices/nm-device.h"
 
 /*****************************************************************************/
 
@@ -684,6 +686,53 @@ check_connection_cloned_mac_address(NMConnection *orig,
 }
 
 static gboolean
+check_connection_controller(NMConnection *orig, NMConnection *candidate, GHashTable *settings)
+{
+    GHashTable           *props;
+    const char           *orig_controller = NULL, *cand_controller = NULL;
+    NMSettingConnection  *s_con_orig, *s_con_cand, *s_con_controller;
+    NMSettingsConnection *con_controller;
+
+    props = check_property_in_hash(settings,
+                                   NM_SETTING_CONNECTION_SETTING_NAME,
+                                   NM_SETTING_CONNECTION_MASTER);
+    if (!props)
+        return TRUE;
+
+    s_con_orig      = nm_connection_get_setting_connection(orig);
+    s_con_cand      = nm_connection_get_setting_connection(candidate);
+    orig_controller = nm_setting_connection_get_master(s_con_orig);
+    cand_controller = nm_setting_connection_get_master(s_con_cand);
+
+    /* A generated connection uses the UUID to specify the controller. Accept
+     * candidates that specify as controller an interface name matching that
+     * UUID */
+    if (orig_controller && cand_controller) {
+        if (nm_utils_is_uuid(orig_controller)) {
+            con_controller = nm_settings_get_connection_by_uuid(NM_SETTINGS_GET, orig_controller);
+            /* no connection found for that uuid */
+            if (!con_controller)
+                return FALSE;
+
+            s_con_controller =
+                nm_settings_connection_get_setting(con_controller, NM_META_SETTING_TYPE_CONNECTION);
+            if (nm_streq0(nm_setting_connection_get_interface_name(s_con_controller),
+                          cand_controller)) {
+                remove_from_hash(settings,
+                                 props,
+                                 NM_SETTING_CONNECTION_SETTING_NAME,
+                                 NM_SETTING_CONNECTION_MASTER);
+                return TRUE;
+            } else {
+                return FALSE;
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+static gboolean
 check_connection_s390_props(NMConnection *orig, NMConnection *candidate, GHashTable *settings)
 {
     GHashTable     *props1, *props2, *props3;
@@ -764,9 +813,16 @@ check_possible_match(NMConnection *orig,
     if (!check_connection_cloned_mac_address(orig, candidate, settings))
         return NULL;
 
+    if (!check_connection_controller(orig, candidate, settings))
+        return NULL;
+
     if (!check_connection_s390_props(orig, candidate, settings))
         return NULL;
 
+    /* match properties are for matching from static to generated connections,
+     * so they are not really part of the difference. */
+    g_hash_table_remove(settings, NM_SETTING_MATCH_SETTING_NAME);
+
     if (g_hash_table_size(settings) == 0)
         return candidate;
     else
@@ -896,6 +952,73 @@ nm_utils_match_connection(NMConnection *const   *connections,
 
 /*****************************************************************************/
 
+const struct _NMMatchSpecDeviceData *
+nm_match_spec_device_data_init_from_device(struct _NMMatchSpecDeviceData *out_data,
+                                           NMDevice                      *device)
+{
+    const char *hw_address;
+    gboolean    is_fake;
+
+    nm_assert(out_data);
+
+    if (!device) {
+        *out_data = (NMMatchSpecDeviceData){};
+        return out_data;
+    }
+
+    nm_assert(NM_IS_DEVICE(device));
+
+    hw_address = nm_device_get_permanent_hw_address_full(
+        device,
+        !nm_device_get_unmanaged_flags(device, NM_UNMANAGED_PLATFORM_INIT),
+        &is_fake);
+
+    /* Note that here we access various getters on @device, without cloning
+     * or taking ownership and return it to the caller.
+     *
+     * The returned data is only valid, until NMDevice gets modified again. */
+
+    *out_data = (NMMatchSpecDeviceData){
+        .interface_name   = nm_device_get_iface(device),
+        .device_type      = nm_device_get_type_description(device),
+        .driver           = nm_device_get_driver(device),
+        .driver_version   = nm_device_get_driver_version(device),
+        .hwaddr           = is_fake ? NULL : hw_address,
+        .s390_subchannels = nm_device_get_s390_subchannels(device),
+        .dhcp_plugin      = nm_dhcp_manager_get_config(nm_dhcp_manager_get()),
+    };
+
+    return out_data;
+}
+
+const NMMatchSpecDeviceData *
+nm_match_spec_device_data_init_from_platform(NMMatchSpecDeviceData *out_data,
+                                             const NMPlatformLink  *pllink,
+                                             const char            *match_device_type,
+                                             const char            *match_dhcp_plugin)
+{
+    nm_assert(out_data);
+
+    /* we can only match by certain properties that are available on the
+     * platform link (and even @pllink might be missing.
+     *
+     * It's still useful because of specs like "*" and "except:interface-name:eth0",
+     * which match even in that case. */
+
+    *out_data = (NMMatchSpecDeviceData){
+        .interface_name   = pllink ? pllink->name : NULL,
+        .device_type      = match_device_type,
+        .driver           = pllink ? pllink->driver : NULL,
+        .driver_version   = NULL,
+        .hwaddr           = NULL,
+        .s390_subchannels = NULL,
+        .dhcp_plugin      = match_dhcp_plugin,
+    };
+    return out_data;
+}
+
+/*****************************************************************************/
+
 int
 nm_match_spec_device_by_pllink(const NMPlatformLink *pllink,
                                const char           *match_device_type,
@@ -903,32 +1026,15 @@ nm_match_spec_device_by_pllink(const NMPlatformLink *pllink,
                                const GSList         *specs,
                                int                   no_match_value)
 {
-    NMMatchSpecMatchType m;
+    NMMatchSpecMatchType  m;
+    NMMatchSpecDeviceData data;
 
-    /* we can only match by certain properties that are available on the
-     * platform link (and even @pllink might be missing.
-     *
-     * It's still useful because of specs like "*" and "except:interface-name:eth0",
-     * which match even in that case. */
     m = nm_match_spec_device(specs,
-                             pllink ? pllink->name : NULL,
-                             match_device_type,
-                             pllink ? pllink->driver : NULL,
-                             NULL,
-                             NULL,
-                             NULL,
-                             match_dhcp_plugin);
-
-    switch (m) {
-    case NM_MATCH_SPEC_MATCH:
-        return TRUE;
-    case NM_MATCH_SPEC_NEG_MATCH:
-        return FALSE;
-    case NM_MATCH_SPEC_NO_MATCH:
-        return no_match_value;
-    }
-    nm_assert_not_reached();
-    return no_match_value;
+                             nm_match_spec_device_data_init_from_platform(&data,
+                                                                          pllink,
+                                                                          match_device_type,
+                                                                          match_dhcp_plugin));
+    return nm_match_spec_match_type_to_bool(m, no_match_value);
 }
 
 /*****************************************************************************/
@@ -1748,6 +1854,13 @@ nm_utils_platform_capture_ip_setting(NMPlatform *platform,
             method = maybe_ipv6_disabled ? NM_SETTING_IP6_CONFIG_METHOD_DISABLED
                                          : NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
     }
+
+    /* The IPv6 method "ignore" and "disabled" are not supported for loopback */
+    if (ifindex == 1
+        && NM_IN_STRSET(method,
+                        NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE))
+        method = NM_SETTING_IP6_CONFIG_METHOD_AUTO;
     g_object_set(s_ip, NM_SETTING_IP_CONFIG_METHOD, method, NULL);
 
     nmp_lookup_init_object_by_ifindex(&lookup, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4), ifindex);
@@ -1860,3 +1973,13 @@ nm_linux_platform_setup_with_tc_cache(void)
 {
     nm_platform_setup(nm_linux_platform_new(NULL, FALSE, FALSE, TRUE));
 }
+
+/*****************************************************************************/
+
+NM_UTILS_FLAGS2STR_DEFINE(
+    nm_settings_autoconnect_blocked_reason_to_string,
+    NMSettingsAutoconnectBlockedReason,
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE, "none"),
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST, "user-request"),
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED, "failed"),
+    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS, "no-secrets"), );
diff --git a/src/core/NetworkManagerUtils.h b/src/core/NetworkManagerUtils.h
index 67c9cba4..7d8afe5a 100644
--- a/src/core/NetworkManagerUtils.h
+++ b/src/core/NetworkManagerUtils.h
@@ -89,6 +89,20 @@ NMConnection *nm_utils_match_connection(NMConnection *const   *connections,
                                         NMUtilsMatchFilterFunc match_filter_func,
                                         gpointer               match_filter_data);
 
+/*****************************************************************************/
+
+struct _NMMatchSpecDeviceData;
+
+const struct _NMMatchSpecDeviceData *
+nm_match_spec_device_data_init_from_device(struct _NMMatchSpecDeviceData *out_data,
+                                           NMDevice                      *device);
+
+const struct _NMMatchSpecDeviceData *
+nm_match_spec_device_data_init_from_platform(struct _NMMatchSpecDeviceData *out_data,
+                                             const NMPlatformLink          *pllink,
+                                             const char                    *match_device_type,
+                                             const char                    *match_dhcp_plugin);
+
 int nm_match_spec_device_by_pllink(const NMPlatformLink *pllink,
                                    const char           *match_device_type,
                                    const char           *match_dhcp_plugin,
@@ -228,6 +242,26 @@ void nm_utils_ip_routes_to_dbus(int                          addr_family,
 
 /*****************************************************************************/
 
+typedef enum _nm_packed {
+    NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE = 0,
+
+    NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST = (1LL << 0),
+    NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED       = (1LL << 1),
+    NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS   = (1LL << 2),
+
+    NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_ALL =
+        (NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST
+         | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED
+         | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS),
+} NMSettingsAutoconnectBlockedReason;
+
+const char *
+nm_settings_autoconnect_blocked_reason_to_string(NMSettingsAutoconnectBlockedReason reason,
+                                                 char                              *buf,
+                                                 gsize                              len);
+
+/*****************************************************************************/
+
 /* For now, all we track about a DHCP lease is the GHashTable with
  * the options.
  *
diff --git a/src/core/devices/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c
index fcd16b1c..89bc84d8 100644
--- a/src/core/devices/adsl/nm-device-adsl.c
+++ b/src/core/devices/adsl/nm-device-adsl.c
@@ -67,13 +67,16 @@ get_generic_capabilities(NMDevice *dev)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMSettingAdsl *s_adsl;
     const char    *protocol;
 
     if (!NM_DEVICE_CLASS(nm_device_adsl_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_adsl = nm_connection_get_setting_adsl(connection);
diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c
index a8258123..8b13e97d 100644
--- a/src/core/devices/bluetooth/nm-device-bt.c
+++ b/src/core/devices/bluetooth/nm-device-bt.c
@@ -192,7 +192,10 @@ can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **speci
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceBt         *self = NM_DEVICE_BT(device);
     NMDeviceBtPrivate  *priv = NM_DEVICE_BT_GET_PRIVATE(self);
@@ -200,7 +203,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     const char         *bdaddr;
 
     if (!NM_DEVICE_CLASS(nm_device_bt_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     if (!get_connection_bt_type_check(self, connection, NULL, error))
diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c
index 870a1c14..f3386ddb 100644
--- a/src/core/devices/nm-device-6lowpan.c
+++ b/src/core/devices/nm-device-6lowpan.c
@@ -72,14 +72,22 @@ create_and_realize(NMDevice              *device,
     s_6lowpan = NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN));
     g_return_val_if_fail(s_6lowpan, FALSE);
 
-    parent_ifindex = parent ? nm_device_get_ifindex(parent) : 0;
+    if (!parent) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+                    "6LoWPAN device can not be created without a parent interface");
+        return FALSE;
+    }
 
+    parent_ifindex = nm_device_get_ifindex(parent);
     if (parent_ifindex <= 0) {
         g_set_error(error,
                     NM_DEVICE_ERROR,
                     NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
-                    "6LoWPAN devices can not be created without a parent interface");
-        g_return_val_if_fail(!parent, FALSE);
+                    "cannot retrieve ifindex of interface %s (%s)",
+                    nm_device_get_iface(parent),
+                    nm_device_get_type_desc(parent));
         return FALSE;
     }
 
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 56c5ec50..10fe8092 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -39,7 +39,8 @@
         NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \
         NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,         \
         NM_SETTING_BOND_OPTION_USE_CARRIER, NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY,       \
-        NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY
+        NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY,      \
+        NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, NM_SETTING_BOND_OPTION_LACP_ACTIVE
 
 #define OPTIONS_REAPPLY_SUBSET                                                             \
     NM_SETTING_BOND_OPTION_MIIMON, NM_SETTING_BOND_OPTION_UPDELAY,                         \
@@ -51,11 +52,12 @@
         NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \
         NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_USE_CARRIER,            \
         NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,      \
-        NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY
+        NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX,    \
+        NM_SETTING_BOND_OPTION_LACP_ACTIVE
 
 #define OPTIONS_REAPPLY_FULL                                     \
     OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \
-        NM_SETTING_BOND_OPTION_ARP_IP_TARGET
+        NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_NS_IP6_TARGET
 
 /*****************************************************************************/
 
@@ -267,7 +269,7 @@ set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new
 
     cur_strv =
         nm_strsplit_set_full(cur_arp_ip_target, NM_ASCII_SPACES, NM_STRSPLIT_SET_FLAGS_STRSTRIP);
-    new_strv = nm_utils_bond_option_arp_ip_targets_split(new_arp_ip_target);
+    new_strv = nm_utils_bond_option_ip_split(new_arp_ip_target);
 
     cur_len = NM_PTRARRAY_LEN(cur_strv);
     new_len = NM_PTRARRAY_LEN(new_strv);
@@ -364,7 +366,7 @@ _bond_arp_ip_target_to_platform(const char *value, in_addr_t out[static NM_BOND_
     int                  i;
     int                  added = 0;
 
-    ip = nm_utils_bond_option_arp_ip_targets_split(value);
+    ip = nm_utils_bond_option_ip_split(value);
 
     if (!ip)
         return added;
@@ -380,6 +382,31 @@ _bond_arp_ip_target_to_platform(const char *value, in_addr_t out[static NM_BOND_
     return added;
 }
 
+static guint8
+_bond_ns_ip6_target_to_platform(const char     *value,
+                                struct in6_addr out[static NM_BOND_MAX_ARP_TARGETS])
+{
+    gs_free const char **ip = NULL;
+    struct in6_addr      in6_a;
+    int                  i;
+    int                  added = 0;
+
+    ip = nm_utils_bond_option_ip_split(value);
+
+    if (!ip)
+        return added;
+
+    for (i = 0; ip[i]; i++) {
+        if (added > NM_BOND_MAX_ARP_TARGETS - 1)
+            break;
+        if (!nm_inet_parse_bin(AF_INET6, ip[i], NULL, &in6_a))
+            nm_assert_not_reached(); /* verify() already validated the IP addresses */
+
+        out[added++] = in6_a;
+    }
+    return added;
+}
+
 static int
 _setting_bond_primary_opt_as_ifindex(NMSettingBond *s_bond)
 {
@@ -436,6 +463,10 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
                                    NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY),
         .num_grat_arp      = _v_u8(s_bond, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP),
         .all_ports_active  = _v_u8(s_bond, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE),
+        .arp_missed_max    = _v_u8(s_bond, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX),
+        .lacp_active       = _v_fcn(_nm_setting_bond_lacp_active_from_string,
+                              s_bond,
+                              NM_SETTING_BOND_OPTION_LACP_ACTIVE),
         .lacp_rate         = _v_fcn(_nm_setting_bond_lacp_rate_from_string,
                             s_bond,
                             NM_SETTING_BOND_OPTION_LACP_RATE),
@@ -455,6 +486,11 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
         props->arp_ip_targets_num =
             _bond_arp_ip_target_to_platform(opt_value, props->arp_ip_target);
 
+    opt_value = nm_setting_bond_get_option_normalized(s_bond, NM_SETTING_BOND_OPTION_NS_IP6_TARGET);
+    if (opt_value != NULL)
+        props->ns_ip6_targets_num =
+            _bond_ns_ip6_target_to_platform(opt_value, props->ns_ip6_target);
+
     props->miimon_has           = !props->arp_interval && !props->arp_validate;
     props->updelay_has          = props->miimon_has && props->miimon;
     props->downdelay_has        = props->miimon_has && props->miimon;
@@ -462,6 +498,7 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
     props->resend_igmp_has      = props->resend_igmp != 1;
     props->lp_interval_has      = props->lp_interval != 1;
     props->tlb_dynamic_lb_has   = NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB);
+    props->lacp_active_has      = NM_IN_SET(props->mode, NM_BOND_MODE_8023AD);
 }
 
 static void
@@ -639,12 +676,14 @@ commit_port_options(NMDevice *bond_device, NMDevice *port, NMSettingBondPort *s_
 
     nm_platform_link_change(nm_device_get_platform(port),
                             nm_device_get_ifindex(port),
+                            NULL,
                             &((NMPlatformLinkBondPort){
                                 .queue_id = s_port ? nm_setting_bond_port_get_queue_id(s_port)
                                                    : NM_BOND_PORT_QUEUE_ID_DEF,
                                 .prio     = prio_has ? prio : 0,
                                 .prio_has = prio_has,
-                            }));
+                            }),
+                            0);
 }
 
 static NMTernary
@@ -686,8 +725,13 @@ attach_port(NMDevice                  *device,
     return TRUE;
 }
 
-static void
-detach_port(NMDevice *device, NMDevice *port, gboolean configure)
+static NMTernary
+detach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     NMDeviceBond *self = NM_DEVICE_BOND(device);
     gboolean      success;
@@ -749,6 +793,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure)
             _LOGI(LOGD_BOND, "bond port %s was detached", nm_device_get_ip_iface(port));
         }
     }
+
+    return TRUE;
 }
 
 static gboolean
diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index c5ce34c2..9a45dbf3 100644
--- a/src/core/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -101,13 +101,16 @@ check_connection_available(NMDevice                      *device,
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMSettingBridge *s_bridge;
     const char      *mac_address;
 
     if (!NM_DEVICE_CLASS(nm_device_bridge_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     if (nm_connection_is_type(connection, NM_SETTING_BLUETOOTH_SETTING_NAME)
@@ -434,96 +437,6 @@ static const Option slave_options[] = {
     OPTION(NM_SETTING_BRIDGE_PORT_HAIRPIN_MODE, "hairpin_mode", OPTION_TYPE_BOOL(FALSE), ),
     {0}};
 
-static void
-commit_option(NMDevice *device, NMSetting *setting, const Option *option, gboolean slave)
-{
-    int                         ifindex = nm_device_get_ifindex(device);
-    nm_auto_unset_gvalue GValue val     = G_VALUE_INIT;
-    GParamSpec                 *pspec;
-    const char                 *value;
-    char                        value_buf[100];
-
-    if (slave)
-        nm_assert(NM_IS_SETTING_BRIDGE_PORT(setting));
-    else
-        nm_assert(NM_IS_SETTING_BRIDGE(setting));
-
-    pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(setting), option->name);
-    nm_assert(pspec);
-
-    g_value_init(&val, G_PARAM_SPEC_VALUE_TYPE(pspec));
-    g_object_get_property((GObject *) setting, option->name, &val);
-
-    if (option->to_sysfs) {
-        value = option->to_sysfs(&val);
-        goto out;
-    }
-
-    switch (pspec->value_type) {
-    case G_TYPE_BOOLEAN:
-        value = g_value_get_boolean(&val) ? "1" : "0";
-        break;
-    case G_TYPE_UINT64:
-    case G_TYPE_UINT:
-    {
-        guint64 uval;
-
-        if (pspec->value_type == G_TYPE_UINT64)
-            uval = g_value_get_uint64(&val);
-        else
-            uval = (guint) g_value_get_uint(&val);
-
-        /* zero means "unspecified" for some NM properties but isn't in the
-             * allowed kernel range, so reset the property to the default value.
-             */
-        if (option->default_if_zero && uval == 0) {
-            if (pspec->value_type == G_TYPE_UINT64)
-                uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(pspec);
-            else
-                uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT(pspec);
-        }
-
-        /* Linux kernel bridge interfaces use 'centiseconds' for time-based values.
-             * In reality it's not centiseconds, but depends on HZ and USER_HZ, which
-             * is almost always works out to be a multiplier of 100, so we can assume
-             * centiseconds.  See clock_t_to_jiffies().
-             */
-        if (option->user_hz_compensate)
-            uval *= 100;
-
-        if (pspec->value_type == G_TYPE_UINT64)
-            nm_sprintf_buf(value_buf, "%" G_GUINT64_FORMAT, uval);
-        else
-            nm_sprintf_buf(value_buf, "%u", (guint) uval);
-
-        value = value_buf;
-    } break;
-    case G_TYPE_STRING:
-        value = g_value_get_string(&val);
-        break;
-    default:
-        nm_assert_not_reached();
-        value = NULL;
-        break;
-    }
-
-out:
-    if (!value)
-        return;
-
-    if (slave) {
-        nm_platform_sysctl_slave_set_option(nm_device_get_platform(device),
-                                            ifindex,
-                                            option->sysname,
-                                            value);
-    } else {
-        nm_platform_sysctl_master_set_option(nm_device_get_platform(device),
-                                             ifindex,
-                                             option->sysname,
-                                             value);
-    }
-}
-
 static const NMPlatformBridgeVlan **
 setting_vlans_to_platform(GPtrArray *array)
 {
@@ -558,19 +471,92 @@ setting_vlans_to_platform(GPtrArray *array)
 }
 
 static void
-commit_slave_options(NMDevice *device, NMSettingBridgePort *setting)
+commit_port_options(NMDevice *device, NMSettingBridgePort *setting)
 {
     const Option              *option;
     NMSetting                 *s;
     gs_unref_object NMSetting *s_clear = NULL;
+    int                        ifindex = nm_device_get_ifindex(device);
 
     if (setting)
         s = NM_SETTING(setting);
     else
         s = s_clear = nm_setting_bridge_port_new();
 
-    for (option = slave_options; option->name; option++)
-        commit_option(device, s, option, TRUE);
+    for (option = slave_options; option->name; option++) {
+        nm_auto_unset_gvalue GValue val = G_VALUE_INIT;
+        GParamSpec                 *pspec;
+        const char                 *value;
+        char                        value_buf[100];
+
+        pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(s), option->name);
+        nm_assert(pspec);
+
+        g_value_init(&val, G_PARAM_SPEC_VALUE_TYPE(pspec));
+        g_object_get_property((GObject *) s, option->name, &val);
+
+        if (option->to_sysfs) {
+            value = option->to_sysfs(&val);
+            goto out;
+        }
+
+        switch (pspec->value_type) {
+        case G_TYPE_BOOLEAN:
+            value = g_value_get_boolean(&val) ? "1" : "0";
+            break;
+        case G_TYPE_UINT64:
+        case G_TYPE_UINT:
+        {
+            guint64 uval;
+
+            if (pspec->value_type == G_TYPE_UINT64)
+                uval = g_value_get_uint64(&val);
+            else
+                uval = (guint) g_value_get_uint(&val);
+
+            /* zero means "unspecified" for some NM properties but isn't in the
+             * allowed kernel range, so reset the property to the default value.
+             */
+            if (option->default_if_zero && uval == 0) {
+                if (pspec->value_type == G_TYPE_UINT64)
+                    uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT64(pspec);
+                else
+                    uval = NM_G_PARAM_SPEC_GET_DEFAULT_UINT(pspec);
+            }
+
+            /* Linux kernel bridge interfaces use 'centiseconds' for time-based values.
+             * In reality it's not centiseconds, but depends on HZ and USER_HZ, which
+             * is almost always works out to be a multiplier of 100, so we can assume
+             * centiseconds.  See clock_t_to_jiffies().
+             */
+            if (option->user_hz_compensate)
+                uval *= 100;
+
+            if (pspec->value_type == G_TYPE_UINT64)
+                nm_sprintf_buf(value_buf, "%" G_GUINT64_FORMAT, uval);
+            else
+                nm_sprintf_buf(value_buf, "%u", (guint) uval);
+
+            value = value_buf;
+        } break;
+        case G_TYPE_STRING:
+            value = g_value_get_string(&val);
+            break;
+        default:
+            nm_assert_not_reached();
+            value = NULL;
+            break;
+        }
+
+out:
+        if (!value)
+            return;
+
+        nm_platform_sysctl_slave_set_option(nm_device_get_platform(device),
+                                            ifindex,
+                                            option->sysname,
+                                            value);
+    }
 }
 
 static void
@@ -746,8 +732,13 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge)
     enabled = nm_setting_bridge_get_vlan_filtering(s_bridge);
 
     if (!enabled) {
-        nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "0");
-        nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", "1");
+        nm_platform_link_set_bridge_info(
+            plat,
+            ifindex,
+            &((NMPlatformLinkSetBridgeInfoData){.vlan_filtering_has    = TRUE,
+                                                .vlan_filtering_val    = FALSE,
+                                                .vlan_default_pvid_has = TRUE,
+                                                .vlan_default_pvid_val = 1}));
         nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL);
         return TRUE;
     }
@@ -762,14 +753,17 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge)
 
     self->vlan_configured = TRUE;
 
-    /* Filtering must be disabled to change the default PVID */
-    if (!nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "0"))
-        return FALSE;
-
-    /* Clear the default PVID so that we later can force the re-creation of
+    /* Filtering must be disabled to change the default PVID.
+     * Clear the default PVID so that we later can force the re-creation of
      * default PVID VLANs by writing the option again. */
-    if (!nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", "0"))
-        return FALSE;
+
+    nm_platform_link_set_bridge_info(
+        plat,
+        ifindex,
+        &((NMPlatformLinkSetBridgeInfoData){.vlan_filtering_has    = TRUE,
+                                            .vlan_filtering_val    = FALSE,
+                                            .vlan_default_pvid_has = TRUE,
+                                            .vlan_default_pvid_val = 0}));
 
     /* Clear all existing VLANs */
     if (!nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, NULL))
@@ -779,11 +773,11 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge)
      * a PVID VLAN on each port, including the bridge itself. */
     pvid = nm_setting_bridge_get_vlan_default_pvid(s_bridge);
     if (pvid) {
-        char value[32];
-
-        nm_sprintf_buf(value, "%u", pvid);
-        if (!nm_platform_sysctl_master_set_option(plat, ifindex, "default_pvid", value))
-            return FALSE;
+        nm_platform_link_set_bridge_info(
+            plat,
+            ifindex,
+            &((NMPlatformLinkSetBridgeInfoData){.vlan_default_pvid_has = TRUE,
+                                                .vlan_default_pvid_val = pvid}));
     }
 
     /* Create VLANs only after setting the default PVID, so that
@@ -793,8 +787,12 @@ bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge)
     if (plat_vlans && !nm_platform_link_set_bridge_vlans(plat, ifindex, FALSE, plat_vlans))
         return FALSE;
 
-    if (!nm_platform_sysctl_master_set_option(plat, ifindex, "vlan_filtering", "1"))
-        return FALSE;
+    nm_platform_link_set_bridge_info(plat,
+                                     ifindex,
+                                     &((NMPlatformLinkSetBridgeInfoData){
+                                         .vlan_filtering_has = TRUE,
+                                         .vlan_filtering_val = TRUE,
+                                     }));
 
     return TRUE;
 }
@@ -1027,7 +1025,7 @@ attach_port(NMDevice                  *device,
                 return FALSE;
         }
 
-        commit_slave_options(port, s_port);
+        commit_port_options(port, s_port);
 
         _LOGI(LOGD_BRIDGE, "attached bridge port %s", nm_device_get_ip_iface(port));
     } else {
@@ -1037,8 +1035,13 @@ attach_port(NMDevice                  *device,
     return TRUE;
 }
 
-static void
-detach_port(NMDevice *device, NMDevice *port, gboolean configure)
+static NMTernary
+detach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     NMDeviceBridge *self = NM_DEVICE_BRIDGE(device);
     gboolean        success;
@@ -1055,7 +1058,7 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure)
 
     if (ifindex_slave <= 0) {
         _LOGD(LOGD_TEAM, "bridge port %s is already detached", nm_device_get_ip_iface(port));
-        return;
+        return TRUE;
     }
 
     if (configure) {
@@ -1071,6 +1074,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure)
     } else {
         _LOGI(LOGD_BRIDGE, "bridge port %s was detached", nm_device_get_ip_iface(port));
     }
+
+    return TRUE;
 }
 
 static gboolean
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index 97cf84a1..aedacc24 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -344,13 +344,16 @@ match_subchans(NMDeviceEthernet *self, NMSettingWired *s_wired, gboolean *try_ma
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceEthernet *self = NM_DEVICE_ETHERNET(device);
     NMSettingWired   *s_wired;
 
     if (!NM_DEVICE_CLASS(nm_device_ethernet_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     if (nm_connection_is_type(connection, NM_SETTING_PPPOE_SETTING_NAME)
diff --git a/src/core/devices/nm-device-factory.h b/src/core/devices/nm-device-factory.h
index ac5ae05f..fc3d9dd4 100644
--- a/src/core/devices/nm-device-factory.h
+++ b/src/core/devices/nm-device-factory.h
@@ -208,8 +208,7 @@ NMDevice *nm_device_factory_create_device(NMDeviceFactory      *factory,
                                                                                                  \
     NM_DEVICE_FACTORY_DECLARE_TYPES(st_code)                                                     \
                                                                                                  \
-    static void nm_##lower##_device_factory_init(NM##mixed##DeviceFactory *self)                 \
-    {}                                                                                           \
+    static void nm_##lower##_device_factory_init(NM##mixed##DeviceFactory *self) {}              \
                                                                                                  \
     static void nm_##lower##_device_factory_class_init(NM##mixed##DeviceFactoryClass *klass)     \
     {                                                                                            \
diff --git a/src/core/devices/nm-device-generic.c b/src/core/devices/nm-device-generic.c
index 9f85925b..c0dcf0de 100644
--- a/src/core/devices/nm-device-generic.c
+++ b/src/core/devices/nm-device-generic.c
@@ -16,7 +16,7 @@
 NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_TYPE_DESCRIPTION, );
 
 typedef struct {
-    char *type_description;
+    const char *type_description;
 } NMDeviceGenericPrivate;
 
 struct _NMDeviceGeneric {
@@ -64,20 +64,23 @@ realize_start_notify(NMDevice *device, const NMPlatformLink *plink)
 
     NM_DEVICE_CLASS(nm_device_generic_parent_class)->realize_start_notify(device, plink);
 
-    nm_clear_g_free(&priv->type_description);
     ifindex = nm_device_get_ip_ifindex(NM_DEVICE(self));
-    if (ifindex > 0)
+    if (ifindex > 0) {
         priv->type_description =
-            g_strdup(nm_platform_link_get_type_name(nm_device_get_platform(device), ifindex));
+            nm_platform_link_get_type_name(nm_device_get_platform(device), ifindex);
+    }
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMSettingConnection *s_con;
 
     if (!NM_DEVICE_CLASS(nm_device_generic_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_con = nm_connection_get_setting_connection(connection);
@@ -125,22 +128,6 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     }
 }
 
-static void
-set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
-    NMDeviceGeneric        *self = NM_DEVICE_GENERIC(object);
-    NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self);
-
-    switch (prop_id) {
-    case PROP_TYPE_DESCRIPTION:
-        priv->type_description = g_value_dup_string(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
 /*****************************************************************************/
 
 static void
@@ -177,17 +164,6 @@ nm_device_generic_new(const NMPlatformLink *plink, gboolean nm_plugin_missing)
                         NULL);
 }
 
-static void
-dispose(GObject *object)
-{
-    NMDeviceGeneric        *self = NM_DEVICE_GENERIC(object);
-    NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self);
-
-    nm_clear_g_free(&priv->type_description);
-
-    G_OBJECT_CLASS(nm_device_generic_parent_class)->dispose(object);
-}
-
 static const NMDBusInterfaceInfoExtended interface_info_device_generic = {
     .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
         NM_DBUS_INTERFACE_DEVICE_GENERIC,
@@ -207,9 +183,7 @@ nm_device_generic_class_init(NMDeviceGenericClass *klass)
     NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
 
     object_class->constructor  = constructor;
-    object_class->dispose      = dispose;
     object_class->get_property = get_property;
-    object_class->set_property = set_property;
 
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_generic);
 
@@ -228,7 +202,7 @@ nm_device_generic_class_init(NMDeviceGenericClass *klass)
                             "",
                             "",
                             NULL,
-                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 }
diff --git a/src/core/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c
index d025931e..6ce61d0b 100644
--- a/src/core/devices/nm-device-infiniband.c
+++ b/src/core/devices/nm-device-infiniband.c
@@ -87,7 +87,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     /* With some drivers the interface must be down to set transport mode */
     nm_device_take_down(device, TRUE);
     ok = nm_platform_sysctl_set(nm_device_get_platform(device),
-                                NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "mode"),
+                                NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_verified, "mode"),
                                 transport_mode);
     nm_device_bring_up(device);
 
@@ -108,15 +108,18 @@ get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *ou
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMSettingInfiniband *s_infiniband;
 
     if (!NM_DEVICE_CLASS(nm_device_infiniband_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
-    if (nm_device_is_real(device)) {
+    if (check_properties && nm_device_is_real(device)) {
         const char *mac;
         const char *hw_addr;
 
diff --git a/src/core/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c
index a5760bf4..cc62180e 100644
--- a/src/core/devices/nm-device-ip-tunnel.c
+++ b/src/core/devices/nm-device-ip-tunnel.c
@@ -498,7 +498,10 @@ update_connection(NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceIPTunnel        *self = NM_DEVICE_IP_TUNNEL(device);
     NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
@@ -507,7 +510,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     const char              *parent;
 
     if (!NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
@@ -520,7 +523,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
         return FALSE;
     }
 
-    if (nm_device_is_real(device)) {
+    if (check_properties && nm_device_is_real(device)) {
         /* Check parent interface; could be an interface name or a UUID */
         parent = nm_setting_ip_tunnel_get_parent(s_ip_tunnel);
         if (parent && !nm_device_match_parent(device, parent)) {
diff --git a/src/core/devices/nm-device-logging.h b/src/core/devices/nm-device-logging.h
index ffc90c2d..53330b5e 100644
--- a/src/core/devices/nm-device-logging.h
+++ b/src/core/devices/nm-device-logging.h
@@ -11,37 +11,37 @@
 #if !_NM_CC_SUPPORT_GENERIC
 #define _NM_DEVICE_CAST(self) ((NMDevice *) (self))
 #elif !defined(_NMLOG_DEVICE_TYPE)
-#define _NM_DEVICE_CAST(self) _Generic((self), NMDevice * : ((NMDevice *) (self)))
+#define _NM_DEVICE_CAST(self) _Generic((self), NMDevice *: ((NMDevice *) (self)))
 #else
-#define _NM_DEVICE_CAST(self)                    \
-    _Generic((self), _NMLOG_DEVICE_TYPE *        \
-             : ((NMDevice *) (self)), NMDevice * \
-             : ((NMDevice *) (self)))
+#define _NM_DEVICE_CAST(self) \
+    _Generic((self), _NMLOG_DEVICE_TYPE *: ((NMDevice *) (self)), NMDevice *: ((NMDevice *) (self)))
 #endif
 
 #undef _NMLOG_ENABLED
 #define _NMLOG_ENABLED(level, domain) (nm_logging_enabled((level), (domain)))
-#define _NMLOG(level, domain, ...)                                                       \
-    G_STMT_START                                                                         \
-    {                                                                                    \
-        const NMLogLevel  _level  = (level);                                             \
-        const NMLogDomain _domain = (domain);                                            \
-                                                                                         \
-        if (nm_logging_enabled(_level, _domain)) {                                       \
-            typeof(*self) *const _self   = (self);                                       \
-            const char *const    _ifname = _nm_device_get_iface(_NM_DEVICE_CAST(_self)); \
-                                                                                         \
-            nm_log_obj(_level,                                                           \
-                       _domain,                                                          \
-                       _ifname,                                                          \
-                       NULL,                                                             \
-                       _self,                                                            \
-                       "device",                                                         \
-                       "%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                    \
-                       NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, ")", "[null]")         \
-                           _NM_UTILS_MACRO_REST(__VA_ARGS__));                           \
-        }                                                                                \
-    }                                                                                    \
+#define _NMLOG(level, domain, ...)                                                                  \
+    G_STMT_START                                                                                    \
+    {                                                                                               \
+        const NMLogLevel  _level  = (level);                                                        \
+        const NMLogDomain _domain = (domain);                                                       \
+                                                                                                    \
+        if (nm_logging_enabled(_level, _domain)) {                                                  \
+            typeof(*self) *const _self   = (self);                                                  \
+            const char *const    _ifname = _nm_device_get_iface(_NM_DEVICE_CAST(_self));            \
+            const char          *_type   = nm_device_get_type_desc_for_log(_NM_DEVICE_CAST(_self)); \
+                                                                                                    \
+            nm_log_obj(_level,                                                                      \
+                       _domain,                                                                     \
+                       _ifname,                                                                     \
+                       NULL,                                                                        \
+                       _self,                                                                       \
+                       "device",                                                                    \
+                       "%s%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                         \
+                       NM_PRINT_FMT_QUOTED(_ifname, "(", _ifname, ")", "[null]"),                   \
+                       NM_PRINT_FMT_QUOTED(_type, "[", _type, "]", "")                              \
+                           _NM_UTILS_MACRO_REST(__VA_ARGS__));                                      \
+        }                                                                                           \
+    }                                                                                               \
     G_STMT_END
 
 #endif /* __NETWORKMANAGER_DEVICE_LOGGING_H__ */
diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c
index 3f57bfb1..8cdef0cf 100644
--- a/src/core/devices/nm-device-macvlan.c
+++ b/src/core/devices/nm-device-macvlan.c
@@ -208,14 +208,22 @@ create_and_realize(NMDevice              *device,
     s_macvlan = nm_connection_get_setting_macvlan(connection);
     g_return_val_if_fail(s_macvlan, FALSE);
 
-    parent_ifindex = parent ? nm_device_get_ifindex(parent) : 0;
+    if (!parent) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
+                    "MACVLAN device can not be created without a parent interface");
+        return FALSE;
+    }
 
+    parent_ifindex = nm_device_get_ifindex(parent);
     if (parent_ifindex <= 0) {
         g_set_error(error,
                     NM_DEVICE_ERROR,
                     NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
-                    "MACVLAN devices can not be created without a parent interface");
-        g_return_val_if_fail(!parent, FALSE);
+                    "cannot retrieve ifindex of interface %s (%s)",
+                    nm_device_get_iface(parent),
+                    nm_device_get_type_desc(parent));
         return FALSE;
     }
 
@@ -274,14 +282,17 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 /*****************************************************************************/
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(device);
     NMSettingMacvlan       *s_macvlan;
     const char             *parent = NULL;
 
     if (!NM_DEVICE_CLASS(nm_device_macvlan_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_macvlan = nm_connection_get_setting_macvlan(connection);
@@ -300,7 +311,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     }
 
     /* Before the device is realized some properties will not be set */
-    if (nm_device_is_real(device)) {
+    if (check_properties && nm_device_is_real(device)) {
         if (setting_mode_to_platform(nm_setting_macvlan_get_mode(s_macvlan)) != priv->props.mode) {
             nm_utils_error_set_literal(error,
                                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
diff --git a/src/core/devices/nm-device-ppp.c b/src/core/devices/nm-device-ppp.c
index 1860df3c..27566607 100644
--- a/src/core/devices/nm-device-ppp.c
+++ b/src/core/devices/nm-device-ppp.c
@@ -149,12 +149,15 @@ _ppp_mgr_callback(NMPppMgr *ppp_mgr, const NMPppMgrCallbackData *callback_data,
 /*****************************************************************************/
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMSettingPppoe *s_pppoe;
 
     if (!NM_DEVICE_CLASS(nm_device_ppp_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_pppoe = nm_connection_get_setting_pppoe(connection);
diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index c597e052..013bc7fd 100644
--- a/src/core/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -29,10 +29,6 @@ enum NMActStageReturn {
 
 #define NM_DEVICE_CAP_INTERNAL_MASK 0xc0000000
 
-NMSettings *nm_device_get_settings(NMDevice *self);
-
-NMManager *nm_device_get_manager(NMDevice *self);
-
 gboolean nm_device_set_ip_ifindex(NMDevice *self, int ifindex);
 
 gboolean nm_device_set_ip_iface(NMDevice *self, const char *iface);
@@ -180,4 +176,6 @@ void nm_device_auth_request(NMDevice                      *self,
                             NMManagerDeviceAuthRequestFunc callback,
                             gpointer                       user_data);
 
+void nm_device_link_properties_set(NMDevice *self, gboolean reapply);
+
 #endif /* NM_DEVICE_PRIVATE_H */
diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c
index cbea7d7d..430e62aa 100644
--- a/src/core/devices/nm-device-tun.c
+++ b/src/core/devices/nm-device-tun.c
@@ -284,7 +284,10 @@ _same_og(const char *str, gboolean og_valid, guint32 og_num)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceTun        *self = NM_DEVICE_TUN(device);
     NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self);
@@ -292,10 +295,10 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     NMSettingTun       *s_tun;
 
     if (!NM_DEVICE_CLASS(nm_device_tun_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
-    if (nm_device_is_real(device)) {
+    if (check_properties && nm_device_is_real(device)) {
         switch (priv->props.type) {
         case IFF_TUN:
             mode = NM_SETTING_TUN_MODE_TUN;
diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c
index feb011db..7849e724 100644
--- a/src/core/devices/nm-device-vlan.c
+++ b/src/core/devices/nm-device-vlan.c
@@ -303,17 +303,20 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 /*****************************************************************************/
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(device);
     NMSettingVlan       *s_vlan;
     const char          *parent;
 
     if (!NM_DEVICE_CLASS(nm_device_vlan_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
-    if (nm_device_is_real(device)) {
+    if (check_properties && nm_device_is_real(device)) {
         s_vlan = nm_connection_get_setting_vlan(connection);
 
         if (nm_setting_vlan_get_id(s_vlan) != priv->vlan_id) {
diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c
index ad31f3a2..a13de1cb 100644
--- a/src/core/devices/nm-device-vrf.c
+++ b/src/core/devices/nm-device-vrf.c
@@ -142,16 +142,19 @@ create_and_realize(NMDevice              *device,
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE(device);
     NMSettingVrf       *s_vrf;
 
     if (!NM_DEVICE_CLASS(nm_device_vrf_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
-    if (nm_device_is_real(device)) {
+    if (check_properties && nm_device_is_real(device)) {
         s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF);
 
         if (priv->props.table != nm_setting_vrf_get_table(s_vrf)) {
@@ -238,8 +241,13 @@ attach_port(NMDevice                  *device,
     return TRUE;
 }
 
-static void
-detach_port(NMDevice *device, NMDevice *port, gboolean configure)
+static NMTernary
+detach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     NMDeviceVrf *self = NM_DEVICE_VRF(device);
     gboolean     success;
@@ -274,6 +282,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure)
             _LOGI(LOGD_DEVICE, "VRF port %s was detached", nm_device_get_ip_iface(port));
         }
     }
+
+    return TRUE;
 }
 
 /*****************************************************************************/
diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c
index 44a7be33..061ee3f2 100644
--- a/src/core/devices/nm-device-vxlan.c
+++ b/src/core/devices/nm-device-vxlan.c
@@ -238,17 +238,20 @@ address_matches(const char *candidate, in_addr_t addr4, struct in6_addr *addr6)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(device);
     NMSettingVxlan       *s_vxlan;
     const char           *parent;
 
     if (!NM_DEVICE_CLASS(nm_device_vxlan_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
-    if (nm_device_is_real(device)) {
+    if (check_properties && nm_device_is_real(device)) {
         s_vxlan = nm_connection_get_setting_vxlan(connection);
 
         parent = nm_setting_vxlan_get_parent(s_vxlan);
diff --git a/src/core/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c
index 98356ccf..282eea87 100644
--- a/src/core/devices/nm-device-wpan.c
+++ b/src/core/devices/nm-device-wpan.c
@@ -75,13 +75,16 @@ update_connection(NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMSettingWpan *s_wpan;
     const char    *mac, *hw_addr;
 
     if (!NM_DEVICE_CLASS(nm_device_wpan_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_wpan = NM_SETTING_WPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_WPAN));
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 62a9ff1e..2038e2f2 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -91,8 +91,8 @@
 
 #define GRACE_PERIOD_MULTIPLIER 2U
 
-#define CARRIER_WAIT_TIME_MS           6000
-#define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000
+#define CARRIER_WAIT_TIME_MS             6000
+#define CARRIER_WAIT_TIME_AFTER_MTU_MSEC 10000
 
 #define NM_DEVICE_AUTH_RETRIES_UNSET    -1
 #define NM_DEVICE_AUTH_RETRIES_INFINITY -2
@@ -132,11 +132,6 @@ typedef struct {
 } SlaveInfo;
 
 typedef struct {
-    NMDevice *device;
-    guint     idle_add_id;
-} DeleteOnDeactivateData;
-
-typedef struct {
     NMDevice               *device;
     GCancellable           *cancellable;
     NMPlatformAsyncCallback callback;
@@ -335,7 +330,6 @@ enum {
     IP6_PREFIX_DELEGATED,
     IP6_SUBNET_NEEDED,
     REMOVED,
-    RECHECK_AUTO_ACTIVATE,
     RECHECK_ASSUME,
     DNS_LOOKUP_DONE,
     PLATFORM_ADDRESS_CHANGED,
@@ -513,8 +507,8 @@ typedef struct _NMDevicePrivate {
 
     NMUnmanagedFlags unmanaged_mask;
     NMUnmanagedFlags unmanaged_flags;
-    DeleteOnDeactivateData
-        *delete_on_deactivate_data; /* data for scheduled cleanup when deleting link (g_idle_add) */
+
+    GSource *delete_on_deactivate_idle_source;
 
     GCancellable *deactivating_cancellable;
 
@@ -542,10 +536,10 @@ typedef struct _NMDevicePrivate {
     /* Link stuff */
     guint             link_connected_id;
     guint             link_disconnected_id;
-    guint             carrier_defer_id;
-    guint             carrier_wait_id;
     gulong            config_changed_id;
     gulong            ifindex_changed_id;
+    GSource          *carrier_wait_source;
+    GSource          *carrier_defer_source;
     guint32           mtu;
     guint32           ip6_mtu; /* FIXME(l3cfg) */
     guint32           mtu_initial;
@@ -559,9 +553,9 @@ typedef struct _NMDevicePrivate {
      * until taking action.
      *
      * When changing MTU, the device might take longer then that. So, whenever
-     * NM changes the MTU it sets @carrier_wait_until_ms to CARRIER_WAIT_TIME_AFTER_MTU_MS
+     * NM changes the MTU it sets @carrier_wait_until_msec to CARRIER_WAIT_TIME_AFTER_MTU_MSEC
      * in the future. This is used to extend the grace period in this particular case. */
-    gint64 carrier_wait_until_ms;
+    gint64 carrier_wait_until_msec;
 
     union {
         struct {
@@ -576,6 +570,8 @@ typedef struct _NMDevicePrivate {
         NMDeviceSysIfaceState       sys_iface_state_;
     };
 
+    NMDeviceSysIfaceState sys_iface_state_before_sleep;
+
     bool carrier : 1;
     bool ignore_carrier : 1;
 
@@ -599,6 +595,8 @@ typedef struct _NMDevicePrivate {
 
     bool tc_committed : 1;
 
+    bool link_props_set : 1;
+
     NMDeviceStageState stage1_sriov_state : 3;
 
     char *current_stable_id;
@@ -702,6 +700,10 @@ typedef struct _NMDevicePrivate {
     GHashTable *ip6_saved_properties;
 
     EthtoolState *ethtool_state;
+    struct {
+        NMPlatformLinkProps       props;
+        NMPlatformLinkChangeFlags flags;
+    } link_props_state;
 
     /* master interface for bridge/bond/team slave */
     NMDevice *master;
@@ -743,6 +745,9 @@ typedef struct _NMDevicePrivate {
     guint   check_delete_unrealized_id;
     guint32 interface_flags;
 
+    guint32             port_detach_count;
+    NMDeviceStateReason port_detach_reason;
+
     struct {
         SriovOp *pending; /* SR-IOV operation currently running */
         SriovOp *next;    /* next SR-IOV operation scheduled */
@@ -863,6 +868,7 @@ static void sriov_op_cb(GError *error, gpointer user_data);
 static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self);
 static gboolean device_link_changed(gpointer user_data);
 static gboolean _get_maybe_ipv6_disabled(NMDevice *self);
+static void     deactivate_ready(NMDevice *self, NMDeviceStateReason reason);
 
 /*****************************************************************************/
 
@@ -2750,6 +2756,152 @@ _ethtool_state_set(NMDevice *self)
         priv->ethtool_state = g_steal_pointer(&ethtool_state);
 }
 
+static NMPlatformLinkChangeFlags
+link_properties_fill_from_setting(NMDevice *self, NMPlatformLinkProps *props)
+{
+    NMPlatformLinkChangeFlags flags = NM_PLATFORM_LINK_CHANGE_NONE;
+    NMSettingLink            *s_link;
+    gint64                    v;
+
+    *props = (NMPlatformLinkProps){};
+
+    s_link = nm_device_get_applied_setting(self, NM_TYPE_SETTING_LINK);
+    if (!s_link)
+        return 0;
+
+    v = nm_setting_link_get_tx_queue_length(s_link);
+    if (v != -1) {
+        props->tx_queue_length = (guint32) v;
+        flags |= NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH;
+    }
+
+    v = nm_setting_link_get_gso_max_size(s_link);
+    if (v != -1) {
+        props->gso_max_size = (guint32) v;
+        flags |= NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE;
+    }
+
+    v = nm_setting_link_get_gso_max_segments(s_link);
+    if (v != -1) {
+        props->gso_max_segments = (guint32) v;
+        flags |= NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS;
+    }
+
+    v = nm_setting_link_get_gro_max_size(s_link);
+    if (v != -1) {
+        props->gro_max_size = (guint32) v;
+        flags |= NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE;
+    }
+
+    return flags;
+}
+
+void
+nm_device_link_properties_set(NMDevice *self, gboolean reapply)
+{
+    NMDevicePrivate          *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMPlatformLinkProps       props;
+    NMPlatformLinkChangeFlags flags;
+    NMPlatform               *platform;
+    const NMPlatformLink     *plink;
+    int                       ifindex;
+
+    ifindex = nm_device_get_ip_ifindex(self);
+    if (ifindex <= 0)
+        return;
+
+    if (priv->link_props_set && !reapply)
+        return;
+
+    priv->link_props_set = TRUE;
+
+    flags = link_properties_fill_from_setting(self, &props);
+
+    if (flags == NM_PLATFORM_LINK_CHANGE_NONE
+        && priv->link_props_state.flags == NM_PLATFORM_LINK_CHANGE_NONE) {
+        /* Nothing to set now, and nothing was set previously. */
+        return;
+    }
+
+    platform = nm_device_get_platform(self);
+
+    if (priv->link_props_state.flags == NM_PLATFORM_LINK_CHANGE_NONE) {
+        /* It's the first time we reach here. Try to fetch the current
+         * link settings (reset them later). */
+        plink = nm_platform_link_get(platform, ifindex);
+        if (plink) {
+            priv->link_props_state.props = plink->link_props;
+            priv->link_props_state.flags = flags;
+        } else {
+            /* Unknown properties. The "priv->link_props_state.flags" stays unset.
+             * It indicates that "priv->link_props_state.props" is unknown. */
+        }
+
+    } else {
+        /* From a previous call we have some "priv->link_props_state.flags"
+         * flags, which indicates that all link props are cached. Also add
+         * "flags" which are are going to set, to indicate that those flags
+         * will need to be reset later. */
+        priv->link_props_state.flags |= flags;
+    }
+
+#define _RESET(_f, _field)                                                                \
+    if (!NM_FLAGS_HAS(flags, (_f)) && NM_FLAGS_HAS(priv->link_props_state.flags, (_f))) { \
+        props._field = priv->link_props_state.props._field;                               \
+        priv->link_props_state.flags &= ~(_f);                                            \
+        flags |= (_f);                                                                    \
+    }
+
+    /* During reapply, if we previously set some "priv->link_props_state.flags"
+     * but now not anymore (according to "flags"), then we reset the value now.
+     *
+     * We do this by copying the props field from "priv->link_props_state" to
+     * "props", reset the flag in "priv->link_props_state.flags" and set the
+     * flag in "flags" (for changing it). */
+    _RESET(NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH, tx_queue_length);
+    _RESET(NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE, gso_max_size);
+    _RESET(NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS, gso_max_segments);
+    _RESET(NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE, gro_max_size);
+
+    if (nm_platform_link_change(platform, ifindex, &props, NULL, flags)) {
+        _LOGD(LOGD_DEVICE, "link properties successfully set");
+    } else {
+        _LOGW(LOGD_DEVICE, "failure setting link properties");
+    }
+}
+
+static void
+link_properties_reset(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMPlatform      *platform;
+    int              ifindex;
+
+    if (priv->link_props_state.flags == 0)
+        goto out;
+
+    ifindex = nm_device_get_ip_ifindex(self);
+    if (ifindex <= 0)
+        goto out;
+
+    platform = nm_device_get_platform(self);
+    nm_assert(platform);
+
+    if (nm_platform_link_change(platform,
+                                ifindex,
+                                &priv->link_props_state.props,
+                                NULL,
+                                priv->link_props_state.flags)) {
+        _LOGD(LOGD_DEVICE, "link properties successfully reset");
+    } else {
+        _LOGW(LOGD_DEVICE, "failure resetting link properties");
+    }
+
+out:
+    priv->link_props_set         = FALSE;
+    priv->link_props_state.flags = 0;
+}
+
 /*****************************************************************************/
 
 gboolean
@@ -2892,6 +3044,7 @@ nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_st
               nm_device_sys_iface_state_to_string(sys_iface_state));
         priv->sys_iface_state_ = sys_iface_state;
         _dev_l3_cfg_commit_type_reset(self);
+        nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, FALSE);
     }
 
     /* this function only sets a flag, no immediate actions are initiated.
@@ -2901,6 +3054,22 @@ nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_st
     nm_assert(priv->sys_iface_state == sys_iface_state);
 }
 
+void
+nm_device_notify_sleeping(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    priv->sys_iface_state_before_sleep = priv->sys_iface_state;
+}
+
+NMDeviceSysIfaceState
+nm_device_get_sys_iface_state_before_sleep(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    return priv->sys_iface_state_before_sleep;
+}
+
 static void
 _active_connection_set_state_flags_full(NMDevice              *self,
                                         NMActivationStateFlags flags,
@@ -3371,7 +3540,7 @@ _dev_ip_state_check(NMDevice *self, int addr_family)
                                 &s_is_pending,
                                 &s_is_failed);
 
-    has_tna = priv->l3cfg && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family);
+    has_tna = priv->l3cfg && nm_l3cfg_has_failedobj_pending(priv->l3cfg, addr_family);
     if (has_tna)
         s_is_pending = TRUE;
 
@@ -3816,9 +3985,7 @@ after_merge_flags:
 }
 
 static gboolean
-_dev_l3_register_l3cds_add_config(NMDevice          *self,
-                                  L3ConfigDataType   l3cd_type,
-                                  NML3CfgConfigFlags flags)
+_dev_l3_register_l3cds_add_config(NMDevice *self, L3ConfigDataType l3cd_type)
 {
     NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
     NML3ConfigMergeFlags merge_flags;
@@ -3841,7 +4008,7 @@ _dev_l3_register_l3cds_add_config(NMDevice          *self,
                                _prop_get_ipvx_dns_priority(self, AF_INET6),
                                acd_defend_type,
                                acd_timeout_msec,
-                               flags,
+                               NM_L3CFG_CONFIG_FLAGS_NONE,
                                merge_flags);
 }
 
@@ -3849,7 +4016,6 @@ static gboolean
 _dev_l3_register_l3cds_set_one_full(NMDevice             *self,
                                     L3ConfigDataType      l3cd_type,
                                     const NML3ConfigData *l3cd,
-                                    NML3CfgConfigFlags    flags,
                                     NMTernary             commit_sync)
 {
     NMDevicePrivate                         *priv     = NM_DEVICE_GET_PRIVATE(self);
@@ -3873,7 +4039,7 @@ _dev_l3_register_l3cds_set_one_full(NMDevice             *self,
 
     if (priv->l3cfg) {
         if (priv->l3cds[l3cd_type].d) {
-            if (_dev_l3_register_l3cds_add_config(self, l3cd_type, flags))
+            if (_dev_l3_register_l3cds_add_config(self, l3cd_type))
                 changed = TRUE;
         }
 
@@ -3897,11 +4063,7 @@ _dev_l3_register_l3cds_set_one(NMDevice             *self,
                                const NML3ConfigData *l3cd,
                                NMTernary             commit_sync)
 {
-    return _dev_l3_register_l3cds_set_one_full(self,
-                                               l3cd_type,
-                                               l3cd,
-                                               NM_L3CFG_CONFIG_FLAGS_NONE,
-                                               commit_sync);
+    return _dev_l3_register_l3cds_set_one_full(self, l3cd_type, l3cd, commit_sync);
 }
 
 static void
@@ -3956,7 +4118,7 @@ _dev_l3_register_l3cds(NMDevice *self,
         }
         if (is_external)
             continue;
-        if (_dev_l3_register_l3cds_add_config(self, i, NM_L3CFG_CONFIG_FLAGS_NONE))
+        if (_dev_l3_register_l3cds_add_config(self, i))
             changed = TRUE;
     }
 
@@ -4110,6 +4272,7 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
             _dev_ipshared4_spawn_dnsmasq(self);
             nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
         }
+        _dev_ip_state_check_async(self, AF_UNSPEC);
         _dev_ipmanual_check_ready(self);
         return;
     case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT:
@@ -4119,10 +4282,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
         return;
     case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
         return;
-    case NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED:
-        /* we commit again. This way we try to configure the routes.*/
-        _dev_l3_cfg_commit(self, FALSE);
-        return;
     case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE:
         if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
                          nmp_object_type_to_flags(NMP_OBJECT_TYPE_LINK)
@@ -4154,9 +4313,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
                  * synchronously to update the current state and schedule a commit. */
                 nm_ndisc_dad_failed(priv->ipac6_data.ndisc, conflicts, TRUE);
             } else if (ready) {
-                if (nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, AF_INET6))
-                    _dev_l3_cfg_commit(self, FALSE);
-
                 nm_clear_l3cd(&priv->ipac6_data.l3cd);
                 _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
                 _dev_ip_state_check_async(self, AF_INET6);
@@ -4736,6 +4892,7 @@ nm_device_parent_find_for_connection(NMDevice *self, const char *current_setting
             && nm_device_check_connection_compatible(
                 parent_device,
                 nm_settings_connection_get_connection(parent_connection),
+                TRUE,
                 NULL))
             return current_setting_parent;
     }
@@ -4959,6 +5116,18 @@ nm_device_get_ip_iface_identifier(NMDevice           *self,
 }
 
 const char *
+nm_device_get_s390_subchannels(NMDevice *self)
+{
+    NMDeviceClass *klass;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
+
+    klass = NM_DEVICE_GET_CLASS(self);
+
+    return klass->get_s390_subchannels ? klass->get_s390_subchannels(self) : NULL;
+}
+
+const char *
 nm_device_get_driver(NMDevice *self)
 {
     g_return_val_if_fail(self != NULL, NULL);
@@ -5243,13 +5412,31 @@ nm_device_get_type_desc(NMDevice *self)
 }
 
 const char *
+nm_device_get_type_desc_for_log(NMDevice *self)
+{
+    const char *type;
+
+    type = nm_device_get_type_desc(self);
+
+    /* Some OVS device types (ports and bridges) are not backed by a kernel link, and
+     * they can have the same name of another device of a different type. In fact, it's
+     * quite common to assign the same name to the OVS bridge, the OVS port and the OVS
+     * interface. For this reason, also log the type in case of OVS devices to make the
+     * log message unambiguous. */
+    if (NM_STR_HAS_PREFIX(type, "Open vSwitch"))
+        return type;
+
+    return NULL;
+}
+
+const char *
 nm_device_get_type_description(NMDevice *self)
 {
     g_return_val_if_fail(self != NULL, NULL);
 
     /* Beware: this function should return the same
-     * value as nm_device_get_type_description() in libnm. */
-
+     * value as nm_device_get_type_description() in libnm.
+     * The returned string is static or interned */
     return NM_DEVICE_GET_CLASS(self)->get_type_description(self);
 }
 
@@ -6162,7 +6349,7 @@ attach_port_cb(NMDevice *self, GError *error, gpointer user_data)
  * nm_device_master_enslave_slave:
  * @self: the master device
  * @slave: the slave device to enslave
- * @connection: (allow-none): the slave device's connection
+ * @connection: (nullable): the slave device's connection
  *
  * If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
  * etc) then this function enslaves @slave.
@@ -6206,6 +6393,21 @@ nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *co
     attach_port_done(self, slave, success);
 }
 
+static void
+detach_port_cb(NMDevice *self, GError *error, gpointer user_data)
+{
+    nm_auto_unref_object NMDevice *slave      = user_data;
+    NMDevicePrivate               *slave_priv = NM_DEVICE_GET_PRIVATE(slave);
+
+    nm_assert(slave_priv->port_detach_count > 0);
+
+    if (--slave_priv->port_detach_count == 0) {
+        if (slave_priv->state == NM_DEVICE_STATE_DEACTIVATING) {
+            deactivate_ready(slave, slave_priv->port_detach_reason);
+        }
+    }
+}
+
 /**
  * nm_device_master_release_slave:
  * @self: the master device
@@ -6262,10 +6464,20 @@ nm_device_master_release_slave(NMDevice           *self,
 
     /* first, let subclasses handle the release ... */
     if (info->slave_is_enslaved || nm_device_sys_iface_state_is_external(slave)
-        || release_type >= RELEASE_SLAVE_TYPE_CONFIG_FORCE)
-        NM_DEVICE_GET_CLASS(self)->detach_port(self,
-                                               slave,
-                                               release_type >= RELEASE_SLAVE_TYPE_CONFIG);
+        || release_type >= RELEASE_SLAVE_TYPE_CONFIG_FORCE) {
+        NMTernary ret;
+
+        ret = NM_DEVICE_GET_CLASS(self)->detach_port(self,
+                                                     slave,
+                                                     release_type >= RELEASE_SLAVE_TYPE_CONFIG,
+                                                     NULL,
+                                                     detach_port_cb,
+                                                     g_object_ref(slave));
+        if (ret == NM_TERNARY_DEFAULT) {
+            slave_priv->port_detach_count++;
+            slave_priv->port_detach_reason = reason;
+        }
+    }
 
     /* raise notifications about the release, including clearing is_enslaved. */
     nm_device_slave_notify_release(slave, reason, release_type);
@@ -6344,13 +6556,6 @@ _dev_unmanaged_check_external_down(NMDevice *self, gboolean only_if_unmanaged, g
     }
 
     ext_flags = _dev_unmanaged_is_external_down(self, FALSE);
-    if (ext_flags != NM_UNMAN_FLAG_OP_SET_UNMANAGED) {
-        /* Ensure the assume check is queued before any queued state changes
-         * from the transition to UNAVAILABLE.
-         */
-        nm_device_queue_recheck_assume(self);
-    }
-
     if (now) {
         nm_device_set_unmanaged_by_flags(self,
                                          NM_UNMANAGED_EXTERNAL_DOWN,
@@ -6434,6 +6639,8 @@ carrier_changed(NMDevice *self, gboolean carrier)
     }
 
     if (carrier) {
+        gboolean recheck_auto_activate = FALSE;
+
         if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) {
             nm_device_queue_state(self,
                                   NM_DEVICE_STATE_DISCONNECTED,
@@ -6444,8 +6651,18 @@ carrier_changed(NMDevice *self, gboolean carrier)
              * when the carrier appears, auto connections are rechecked for
              * the device.
              */
-            nm_device_emit_recheck_auto_activate(self);
+            recheck_auto_activate = TRUE;
         }
+        if (nm_manager_devcon_autoconnect_blocked_reason_set(
+                nm_device_get_manager(self),
+                self,
+                NULL,
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
+                FALSE))
+            recheck_auto_activate = TRUE;
+
+        if (recheck_auto_activate)
+            nm_device_recheck_auto_activate_schedule(self);
     } else {
         if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) {
             if (priv->queued_state.id && priv->queued_state.state >= NM_DEVICE_STATE_DISCONNECTED)
@@ -6464,24 +6681,20 @@ carrier_disconnected_action_cb(gpointer user_data)
     NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    _LOGD(LOGD_DEVICE,
-          "carrier: link disconnected (calling deferred action) (id=%u)",
-          priv->carrier_defer_id);
+    _LOGD(LOGD_DEVICE, "carrier: link disconnected (calling deferred action)");
 
-    priv->carrier_defer_id = 0;
+    nm_clear_g_source_inst(&priv->carrier_defer_source);
     carrier_changed(self, FALSE);
-    return FALSE;
+    return G_SOURCE_CONTINUE;
 }
 
 static void
 carrier_disconnected_action_cancel(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    guint            id   = priv->carrier_defer_id;
 
-    if (nm_clear_g_source(&priv->carrier_defer_id)) {
-        _LOGD(LOGD_DEVICE, "carrier: link disconnected (canceling deferred action) (id=%u)", id);
-    }
+    if (nm_clear_g_source_inst(&priv->carrier_defer_source))
+        _LOGD(LOGD_DEVICE, "carrier: link disconnected (canceling deferred action)");
 }
 
 void
@@ -6509,28 +6722,29 @@ nm_device_set_carrier(NMDevice *self, gboolean carrier)
         NM_DEVICE_GET_CLASS(self)->carrier_changed_notify(self, carrier);
         carrier_changed(self, TRUE);
 
-        if (priv->carrier_wait_id) {
+        if (priv->carrier_wait_source) {
             nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
             _carrier_wait_check_queued_act_request(self);
         }
     } else {
-        if (priv->carrier_wait_id)
+        if (priv->carrier_wait_source)
             nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
         NM_DEVICE_GET_CLASS(self)->carrier_changed_notify(self, carrier);
         if (state <= NM_DEVICE_STATE_DISCONNECTED && !priv->queued_act_request) {
             _LOGD(LOGD_DEVICE, "carrier: link disconnected");
+            carrier_disconnected_action_cancel(self);
             carrier_changed(self, FALSE);
-        } else {
-            gint64 now_ms, until_ms;
+        } else if (!priv->carrier_defer_source) {
+            gint64 until_ms;
+            gint64 now_ms;
 
             now_ms   = nm_utils_get_monotonic_timestamp_msec();
-            until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms);
-            priv->carrier_defer_id =
-                g_timeout_add(until_ms - now_ms, carrier_disconnected_action_cb, self);
+            until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_msec);
+            priv->carrier_defer_source =
+                nm_g_timeout_add_source(until_ms - now_ms, carrier_disconnected_action_cb, self);
             _LOGD(LOGD_DEVICE,
-                  "carrier: link disconnected (deferring action for %ld milliseconds) (id=%u)",
-                  (long) (until_ms - now_ms),
-                  priv->carrier_defer_id);
+                  "carrier: link disconnected (deferring action for %ld milliseconds)",
+                  (long) (until_ms - now_ms));
         }
     }
 }
@@ -6682,6 +6896,37 @@ device_update_interface_flags(NMDevice *self, const NMPlatformLink *plink)
                              TRUE);
 }
 
+/*
+ * Returns the reason for managing a device. The suffix "external" indicates
+ * that the reason mainly depends on whether we want to make the device
+ * sys-iface-state=external or not.
+ */
+NMDeviceStateReason
+nm_device_get_manage_reason_external(NMDevice *self)
+{
+    NMDeviceStateReason reason;
+
+    /* By default we return reason NOW_MANAGED, which makes the device fully
+     * managed by NM (sys-iface-state=managed). */
+    reason = NM_DEVICE_STATE_REASON_NOW_MANAGED;
+
+    /* If the device is an external-down candidate but no longer has the flag
+     * set, then the device is an externally created interface that previously
+     * had no addresses or no controller and now has.
+     * We need to set CONNECTION_ASSUMED as the reason, so that the device
+     * is managed but is not touched by NM (sys-iface-state=external). */
+    if (nm_device_get_unmanaged_mask(self, NM_UNMANAGED_EXTERNAL_DOWN)
+        && !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_EXTERNAL_DOWN)) {
+        /* user-udev overwrites external-down, so we only assume the device
+         * when it is a external-down candidate which is not managed via udev. */
+        if (!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_USER_UDEV)) {
+            reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
+        }
+    }
+
+    return reason;
+}
+
 static gboolean
 device_link_changed(gpointer user_data)
 {
@@ -6768,7 +7013,7 @@ device_link_changed(gpointer user_data)
         /* Let any connections that use the new interface name have a chance
          * to auto-activate on the device.
          */
-        nm_device_emit_recheck_auto_activate(self);
+        nm_device_recheck_auto_activate_schedule(self);
     }
 
     if (priv->ipac6_data.ndisc && pllink->inet6_token.id) {
@@ -6793,35 +7038,13 @@ device_link_changed(gpointer user_data)
     priv->up = NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP);
 
     if (pllink->initialized && nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) {
-        NMDeviceStateReason reason;
-
         nm_device_set_unmanaged_by_user_udev(self);
         nm_device_set_unmanaged_by_user_conf(self);
 
-        reason = NM_DEVICE_STATE_REASON_NOW_MANAGED;
-
-        /* If the device is a external-down candidated but no longer has external
-         * down set, we must clear the platform-unmanaged flag with reason
-         * "assumed". */
-        if (nm_device_get_unmanaged_mask(self, NM_UNMANAGED_EXTERNAL_DOWN)
-            && !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_EXTERNAL_DOWN)) {
-            /* actually, user-udev overwrites external-down. So we only assume the device,
-             * when it is a external-down candidate, which is not managed via udev. */
-            if (!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_USER_UDEV)) {
-                /* Ensure the assume check is queued before any queued state changes
-                 * from the transition to UNAVAILABLE.
-                 */
-                reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
-            }
-        }
-
-        /* The assume check should happen before the device transitions to
-         * UNAVAILABLE, because in UNAVAILABLE we already clean up the IP
-         * configuration. Therefore, this function should never trigger a
-         * sync state transition.
-         */
-        nm_device_queue_recheck_assume(self);
-        nm_device_set_unmanaged_by_flags_queue(self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason);
+        nm_device_set_unmanaged_by_flags_queue(self,
+                                               NM_UNMANAGED_PLATFORM_INIT,
+                                               NM_UNMAN_FLAG_OP_SET_MANAGED,
+                                               nm_device_get_manage_reason_external(self));
     }
 
     _dev_unmanaged_check_external_down(self, FALSE, FALSE);
@@ -7309,14 +7532,15 @@ device_init_static_sriov_num_vfs(NMDevice *self)
     if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)) {
         int num_vfs;
 
-        num_vfs = nm_config_data_get_device_config_int64(NM_CONFIG_GET_DATA,
-                                                         NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
-                                                         self,
-                                                         10,
-                                                         0,
-                                                         G_MAXINT32,
-                                                         -1,
-                                                         -1);
+        num_vfs = nm_config_data_get_device_config_int64_by_device(
+            NM_CONFIG_GET_DATA,
+            NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
+            self,
+            10,
+            0,
+            G_MAXINT32,
+            -1,
+            -1);
         if (num_vfs >= 0)
             sriov_op_queue(self, num_vfs, NM_OPTION_BOOL_DEFAULT, NULL, NULL);
     }
@@ -7332,7 +7556,7 @@ config_changed(NMConfig           *config,
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     if (priv->state <= NM_DEVICE_STATE_DISCONNECTED || priv->state >= NM_DEVICE_STATE_ACTIVATED) {
-        priv->ignore_carrier = nm_config_data_get_ignore_carrier(config_data, self);
+        priv->ignore_carrier = nm_config_data_get_ignore_carrier_by_device(config_data, self);
         if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES)
             && !nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))
             device_init_static_sriov_num_vfs(self);
@@ -7471,8 +7695,9 @@ realize_start_setup(NMDevice             *self,
     nm_device_update_permanent_hw_address(self, FALSE);
 
     /* Note: initial hardware address must be read before calling get_ignore_carrier() */
-    config               = nm_config_get();
-    priv->ignore_carrier = nm_config_data_get_ignore_carrier(nm_config_get_data(config), self);
+    config = nm_config_get();
+    priv->ignore_carrier =
+        nm_config_data_get_ignore_carrier_by_device(nm_config_get_data(config), self);
     if (!priv->config_changed_id) {
         priv->config_changed_id = g_signal_connect(config,
                                                    NM_CONFIG_SIGNAL_CONFIG_CHANGED,
@@ -7701,6 +7926,10 @@ nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error)
     /* Garbage-collect unneeded unrealized devices. */
     nm_device_recheck_available_connections(self);
 
+    /* In case the unrealized device is not going away, it may need to
+     * autoactivate.  Schedule also a check for that. */
+    nm_device_recheck_auto_activate_schedule(self);
+
     return TRUE;
 }
 
@@ -7721,7 +7950,7 @@ nm_device_notify_availability_maybe_changed(NMDevice *self)
      * available. */
     nm_device_recheck_available_connections(self);
     if (g_hash_table_size(priv->available_connections) > 0)
-        nm_device_emit_recheck_auto_activate(self);
+        nm_device_recheck_auto_activate_schedule(self);
 }
 
 /**
@@ -7880,7 +8109,7 @@ nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure)
         g_warn_if_fail(!NM_FLAGS_HAS(slave_priv->unmanaged_mask, NM_UNMANAGED_IS_SLAVE));
         nm_device_set_unmanaged_by_flags(slave,
                                          NM_UNMANAGED_IS_SLAVE,
-                                         FALSE,
+                                         NM_UNMAN_FLAG_OP_SET_MANAGED,
                                          NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
         changed = TRUE;
     } else
@@ -8331,7 +8560,7 @@ nm_device_autoconnect_allowed(NMDevice *self)
             return FALSE;
     }
 
-    if (priv->delete_on_deactivate_data)
+    if (priv->delete_on_deactivate_idle_source)
         return FALSE;
 
     /* The 'autoconnect-allowed' signal is emitted on a device to allow
@@ -8450,7 +8679,7 @@ device_has_config(NMDevice *self)
  * @self: the master #NMDevice
  * @slave: the slave #NMDevice
  * @connection: the #NMConnection to update with the slave settings
- * @GError: (out): error description
+ * @error: error description
  *
  * Reads the slave configuration for @slave and updates @connection with those
  * properties. This invokes a virtual function on the master device @self.
@@ -8719,7 +8948,7 @@ nm_device_complete_connection(NMDevice            *self,
     if (!nm_connection_normalize(connection, NULL, NULL, error))
         return FALSE;
 
-    return nm_device_check_connection_compatible(self, connection, error);
+    return nm_device_check_connection_compatible(self, connection, TRUE, error);
 }
 
 gboolean
@@ -8779,7 +9008,10 @@ nm_device_match_parent_hwaddr(NMDevice     *device,
 }
 
 static gboolean
-check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *self,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDevicePrivate      *priv         = NM_DEVICE_GET_PRIVATE(self);
     const char           *device_iface = nm_device_get_iface(self);
@@ -8906,12 +9138,18 @@ check_connection_compatible(NMDevice *self, NMConnection *connection, GError **e
  *   @self.
  */
 gboolean
-nm_device_check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error)
+nm_device_check_connection_compatible(NMDevice     *self,
+                                      NMConnection *connection,
+                                      gboolean      check_properties,
+                                      GError      **error)
 {
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
     g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);
 
-    return NM_DEVICE_GET_CLASS(self)->check_connection_compatible(self, connection, error);
+    return NM_DEVICE_GET_CLASS(self)->check_connection_compatible(self,
+                                                                  connection,
+                                                                  check_properties,
+                                                                  error);
 }
 
 gboolean
@@ -9077,9 +9315,9 @@ nm_device_queue_recheck_available(NMDevice           *self,
 }
 
 void
-nm_device_emit_recheck_auto_activate(NMDevice *self)
+nm_device_recheck_auto_activate_schedule(NMDevice *self)
 {
-    g_signal_emit(self, signals[RECHECK_AUTO_ACTIVATE], 0);
+    nm_manager_device_recheck_auto_activate_schedule(nm_device_get_manager(self), self);
 }
 
 void
@@ -9352,11 +9590,7 @@ sriov_params_cb(GError *error, gpointer user_data)
     if (!nm_platform_link_set_sriov_vfs(nm_device_get_platform(self),
                                         priv->ifindex,
                                         (const NMPlatformVF *const *) plat_vfs)) {
-        _LOGE(LOGD_DEVICE, "failed to apply SR-IOV VFs");
-        nm_device_state_changed(self,
-                                NM_DEVICE_STATE_FAILED,
-                                NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
-        return;
+        _LOGW(LOGD_DEVICE, "failed to apply SR-IOV VF configurations");
     }
 
     priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED;
@@ -9757,10 +9991,12 @@ activate_stage2_device_config(NMDevice *self)
 
     nm_device_state_changed(self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
-    if (!nm_device_sys_iface_state_is_external_or_assume(self))
+    if (!nm_device_sys_iface_state_is_external(self)) {
         _ethtool_state_set(self);
+        nm_device_link_properties_set(self, FALSE);
+    }
 
-    if (!nm_device_sys_iface_state_is_external_or_assume(self)) {
+    if (!nm_device_sys_iface_state_is_external(self)) {
         if (!priv->tc_committed && !tc_commit(self)) {
             _LOGW(LOGD_DEVICE, "failed applying traffic control rules");
             nm_device_state_changed(self,
@@ -10161,14 +10397,6 @@ _dev_ipmanual_check_ready(NMDevice *self)
             _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED);
             _dev_ip_state_check_async(self, AF_UNSPEC);
         } else if (ready) {
-            if (priv->ipmanual_data.state_x[IS_IPv4] != NM_DEVICE_IP_STATE_READY
-                && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family)) {
-                /* Addresses with pending ACD/DAD are a possible cause for the
-                 * presence of temporarily-not-available objects. Once all addresses
-                 * are ready, retry to commit those unavailable objects. */
-                _dev_l3_cfg_commit(self, FALSE);
-            }
-
             _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
             _dev_ip_state_check_async(self, AF_UNSPEC);
         }
@@ -10334,7 +10562,6 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d
         _dev_l3_register_l3cds_set_one_full(self,
                                             L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4),
                                             notify_data->lease_update.l3cd,
-                                            NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
                                             FALSE);
 
         if (notify_data->lease_update.accepted) {
@@ -10471,6 +10698,7 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
             .addr_family             = AF_INET,
             .l3cfg                   = nm_device_get_l3cfg(self),
             .iface                   = nm_device_get_ip_iface(self),
+            .iface_type_log          = nm_device_get_type_desc_for_log(self),
             .uuid                    = nm_connection_get_uuid(connection),
             .hwaddr                  = hwaddr,
             .bcast_hwaddr            = bcast_hwaddr,
@@ -10499,6 +10727,7 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
         gboolean               iaid_explicit;
         guint32                iaid;
         NMDhcpClientConfig     config;
+        const char            *pd_hint;
 
         iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, FALSE, &iaid_explicit);
         duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid);
@@ -10507,6 +10736,7 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
             .addr_family     = AF_INET6,
             .l3cfg           = nm_device_get_l3cfg(self),
             .iface           = nm_device_get_ip_iface(self),
+            .iface_type_log  = nm_device_get_type_desc_for_log(self),
             .uuid            = nm_connection_get_uuid(connection),
             .send_hostname   = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
             .hostname        = nm_setting_ip_config_get_dhcp_hostname(s_ip),
@@ -10525,6 +10755,21 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
                 },
         };
 
+        pd_hint = nm_setting_ip6_config_get_dhcp_pd_hint(NM_SETTING_IP6_CONFIG(s_ip));
+        if (pd_hint) {
+            int      pd_hint_length;
+            gboolean res;
+
+            res = nm_inet_parse_with_prefix_bin(AF_INET6,
+                                                pd_hint,
+                                                NULL,
+                                                &config.v6.pd_hint_addr,
+                                                &pd_hint_length);
+            nm_assert(res);
+            nm_assert(pd_hint_length > 0 && pd_hint_length <= 128);
+            config.v6.pd_hint_length = pd_hint_length;
+        }
+
         priv->ipdhcp_data_6.client =
             nm_dhcp_manager_start_client(nm_dhcp_manager_get(), &config, &error);
     }
@@ -10555,7 +10800,6 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
         _dev_l3_register_l3cds_set_one_full(self,
                                             L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4),
                                             previous_lease,
-                                            NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
                                             FALSE);
     }
 
@@ -10680,10 +10924,13 @@ connection_ip_method_requires_carrier(NMConnection *connection,
 static gboolean
 connection_requires_carrier(NMConnection *connection)
 {
-    NMSettingIPConfig   *s_ip4, *s_ip6;
+    NMSettingIPConfig   *s_ip4;
+    NMSettingIPConfig   *s_ip6;
     NMSettingConnection *s_con;
-    gboolean             ip4_carrier_wanted, ip6_carrier_wanted;
-    gboolean             ip4_used = FALSE, ip6_used = FALSE;
+    gboolean             ip4_carrier_wanted;
+    gboolean             ip6_carrier_wanted;
+    gboolean             ip4_used = FALSE;
+    gboolean             ip6_used = FALSE;
 
     /* We can progress to IP_CONFIG now, so that we're enslaved.
      * That may actually cause carrier to go up and thus continue activation. */
@@ -11196,10 +11443,8 @@ _commit_mtu(NMDevice *self)
     if (ifindex <= 0)
         return;
 
-    if (!nm_device_get_applied_connection(self)
-        || nm_device_sys_iface_state_is_external_or_assume(self)) {
-        /* we don't tamper with the MTU of disconnected and
-         * external/assumed devices. */
+    if (!nm_device_get_applied_connection(self) || nm_device_sys_iface_state_is_external(self)) {
+        /* we don't tamper with the MTU of disconnected and external devices. */
         return;
     }
 
@@ -11378,8 +11623,8 @@ _commit_mtu(NMDevice *self)
                                  ? "Are the MTU sizes of the slaves large enough?"
                                  : "Did you configure the MTU correctly?"));
             }
-            priv->carrier_wait_until_ms =
-                nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+            priv->carrier_wait_until_msec =
+                nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MSEC;
         }
 
         if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS()) {
@@ -11408,8 +11653,8 @@ _commit_mtu(NMDevice *self)
                        msg ? ": " : "",
                        msg ?: "");
             }
-            priv->carrier_wait_until_ms =
-                nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+            priv->carrier_wait_until_msec =
+                nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MSEC;
         }
     }
 
@@ -11488,11 +11733,7 @@ _dev_ipac6_ndisc_config_changed(NMNDisc              *ndisc,
 
     _dev_ipac6_grace_period_start(self, 0, TRUE);
 
-    _dev_l3_register_l3cds_set_one_full(self,
-                                        L3_CONFIG_DATA_TYPE_AC_6,
-                                        l3cd,
-                                        NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
-                                        FALSE);
+    _dev_l3_register_l3cds_set_one_full(self, L3_CONFIG_DATA_TYPE_AC_6, l3cd, FALSE);
 
     nm_clear_l3cd(&priv->ipac6_data.l3cd);
     ready = nm_l3cfg_check_ready(priv->l3cfg,
@@ -11682,7 +11923,11 @@ _dev_ipac6_start(NMDevice *self)
     }
 
     if (nm_device_get_ip_iface_identifier(self, &iid, FALSE, &is_token)) {
-        _LOGD_ipac6("using the device EUI-64 identifier");
+        char buf[INET6_ADDRSTRLEN];
+
+        _LOGD_ipac6("using the device EUI-64 identifier %s (from %s)",
+                    nm_utils_inet6_interface_identifier_to_token(&iid, buf),
+                    is_token ? "token" : "address");
         nm_ndisc_set_iid(priv->ipac6_data.ndisc, iid, is_token);
     } else {
         /* Don't abort the addrconf at this point -- if ndisc needs the iid
@@ -12610,24 +12855,24 @@ nm_device_is_nm_owned(NMDevice *self)
 static gboolean
 delete_on_deactivate_link_delete(gpointer user_data)
 {
-    DeleteOnDeactivateData        *data  = user_data;
-    nm_auto_unref_object NMDevice *self  = data->device;
+    nm_auto_unref_object NMDevice *self  = user_data;
     NMDevicePrivate               *priv  = NM_DEVICE_GET_PRIVATE(self);
     gs_free_error GError          *error = NULL;
 
-    _LOGD(LOGD_DEVICE,
-          "delete_on_deactivate: cleanup and delete virtual link (id=%u)",
-          data->idle_add_id);
+    _LOGD(LOGD_DEVICE, "delete_on_deactivate: cleanup and delete virtual link");
 
-    priv->delete_on_deactivate_data = NULL;
+    nm_clear_g_source_inst(&priv->delete_on_deactivate_idle_source);
 
     if (!nm_device_unrealize(self, TRUE, &error))
         _LOGD(LOGD_DEVICE, "delete_on_deactivate: unrealizing failed (%s)", error->message);
 
-    nm_device_emit_recheck_auto_activate(self);
+    if (nm_dbus_object_is_exported(NM_DBUS_OBJECT(self))) {
+        /* The device is still alive. We may need to autoactivate virtual
+         * devices again. */
+        nm_device_recheck_auto_activate_schedule(self);
+    }
 
-    g_free(data);
-    return FALSE;
+    return G_SOURCE_CONTINUE;
 }
 
 static void
@@ -12635,25 +12880,16 @@ delete_on_deactivate_unschedule(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (priv->delete_on_deactivate_data) {
-        DeleteOnDeactivateData *data = priv->delete_on_deactivate_data;
-
-        priv->delete_on_deactivate_data = NULL;
-
-        g_source_remove(data->idle_add_id);
-        _LOGD(LOGD_DEVICE,
-              "delete_on_deactivate: cancel cleanup and delete virtual link (id=%u)",
-              data->idle_add_id);
-        g_object_unref(data->device);
-        g_free(data);
+    if (nm_clear_g_source_inst(&priv->delete_on_deactivate_idle_source)) {
+        _LOGD(LOGD_DEVICE, "delete_on_deactivate: cancel cleanup and delete virtual link");
+        g_object_unref(self);
     }
 }
 
 static void
 delete_on_deactivate_check_and_schedule(NMDevice *self)
 {
-    NMDevicePrivate        *priv = NM_DEVICE_GET_PRIVATE(self);
-    DeleteOnDeactivateData *data;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     if (!priv->nm_owned)
         return;
@@ -12663,18 +12899,13 @@ delete_on_deactivate_check_and_schedule(NMDevice *self)
         return;
     if (nm_device_get_state(self) == NM_DEVICE_STATE_UNMANAGED)
         return;
-    if (nm_device_get_state(self) == NM_DEVICE_STATE_UNAVAILABLE)
-        return;
-    delete_on_deactivate_unschedule(self); /* always cancel and reschedule */
 
-    data                            = g_new(DeleteOnDeactivateData, 1);
-    data->device                    = g_object_ref(self);
-    data->idle_add_id               = g_idle_add(delete_on_deactivate_link_delete, data);
-    priv->delete_on_deactivate_data = data;
+    g_object_ref(self);
+    delete_on_deactivate_unschedule(self); /* always cancel and reschedule */
+    priv->delete_on_deactivate_idle_source =
+        nm_g_idle_add_source(delete_on_deactivate_link_delete, self);
 
-    _LOGD(LOGD_DEVICE,
-          "delete_on_deactivate: schedule cleanup and delete virtual link (id=%u)",
-          data->idle_add_id);
+    _LOGD(LOGD_DEVICE, "delete_on_deactivate: schedule cleanup and delete virtual link");
 }
 
 static void
@@ -12835,7 +13066,8 @@ can_reapply_change(NMDevice   *self,
                      NM_SETTING_USER_SETTING_NAME,
                      NM_SETTING_PROXY_SETTING_NAME,
                      NM_SETTING_IP4_CONFIG_SETTING_NAME,
-                     NM_SETTING_IP6_CONFIG_SETTING_NAME))
+                     NM_SETTING_IP6_CONFIG_SETTING_NAME,
+                     NM_SETTING_LINK_SETTING_NAME))
         return TRUE;
 
     if (nm_streq(setting_name, NM_SETTING_WIRED_SETTING_NAME)) {
@@ -12884,7 +13116,7 @@ reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new)
  * Change configuration of an already configured device if possible.
  * Updates the device's applied connection upon success.
  *
- * Return: %FALSE if the new configuration can not be reapplied.
+ * Returns: %FALSE if the new configuration can not be reapplied.
  */
 static gboolean
 check_and_reapply_connection(NMDevice            *self,
@@ -13032,6 +13264,8 @@ check_and_reapply_connection(NMDevice            *self,
      *************************************************************************/
     klass->reapply_connection(self, con_old, con_new);
 
+    nm_device_link_properties_set(self, TRUE);
+
     if (priv->state >= NM_DEVICE_STATE_CONFIG)
         lldp_setup(self, NM_TERNARY_DEFAULT);
 
@@ -13079,7 +13313,7 @@ check_and_reapply_connection(NMDevice            *self,
     if (sett_conn) {
         nm_settings_connection_autoconnect_blocked_reason_set(
             sett_conn,
-            NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+            NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST,
             FALSE);
     }
 
@@ -13414,7 +13648,8 @@ delete_cb(NMDevice              *self,
           GError                *error,
           gpointer               user_data)
 {
-    GError *local = NULL;
+    NMSettingsConnection *sett_conn;
+    GError               *local = NULL;
 
     if (error) {
         g_dbus_method_invocation_return_gerror(context, error);
@@ -13429,10 +13664,26 @@ delete_cb(NMDevice              *self,
 
     /* Authorized */
     nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DELETE, self, TRUE, NULL, subject, NULL);
-    if (nm_device_unrealize(self, TRUE, &local))
-        g_dbus_method_invocation_return_value(context, NULL);
-    else
+
+    sett_conn = nm_device_get_settings_connection(self);
+    if (sett_conn) {
+        /* Block profile from autoconnecting. We block the profile, which may
+         * be ugly/wrong with multi-connect profiles. However, it's not
+         * obviously wrong, because profiles for software devices tend not to
+         * work with multi-connect anyway, because they describe a (unique)
+         * interface by name. */
+        nm_settings_connection_autoconnect_blocked_reason_set(
+            sett_conn,
+            NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST,
+            TRUE);
+    }
+
+    if (!nm_device_unrealize(self, TRUE, &local)) {
         g_dbus_method_invocation_take_error(context, local);
+        return;
+    }
+
+    g_dbus_method_invocation_return_value(context, NULL);
 }
 
 static void
@@ -13539,7 +13790,7 @@ _carrier_wait_check_act_request_must_queue(NMDevice *self, NMActRequest *req)
      * request is not blocked waiting for carrier. */
     if (priv->carrier)
         return FALSE;
-    if (priv->carrier_wait_id == 0)
+    if (!priv->carrier_wait_source)
         return FALSE;
 
     connection = nm_act_request_get_applied_connection(req);
@@ -14053,11 +14304,11 @@ carrier_wait_timeout(gpointer user_data)
     NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    priv->carrier_wait_id = 0;
+    nm_clear_g_source_inst(&priv->carrier_wait_source);
     nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
     if (!priv->carrier)
         _carrier_wait_check_queued_act_request(self);
-    return G_SOURCE_REMOVE;
+    return G_SOURCE_CONTINUE;
 }
 
 static gboolean
@@ -14074,14 +14325,15 @@ nm_device_is_up(NMDevice *self)
 static gint64
 _get_carrier_wait_ms(NMDevice *self)
 {
-    return nm_config_data_get_device_config_int64(NM_CONFIG_GET_DATA,
-                                                  NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
-                                                  self,
-                                                  10,
-                                                  0,
-                                                  G_MAXINT32,
-                                                  CARRIER_WAIT_TIME_MS,
-                                                  CARRIER_WAIT_TIME_MS);
+    return nm_config_data_get_device_config_int64_by_device(
+        NM_CONFIG_GET_DATA,
+        NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
+        self,
+        10,
+        0,
+        G_MAXINT32,
+        CARRIER_WAIT_TIME_MS,
+        CARRIER_WAIT_TIME_MS);
 }
 
 /*
@@ -14104,13 +14356,14 @@ carrier_detect_wait(NMDevice *self)
      *
      * If during that time carrier goes away, we declare the interface
      * as not ready. */
-    nm_clear_g_source(&priv->carrier_wait_id);
+    nm_clear_g_source_inst(&priv->carrier_wait_source);
     if (!priv->carrier)
         nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
 
     now_ms   = nm_utils_get_monotonic_timestamp_msec();
-    until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms);
-    priv->carrier_wait_id = g_timeout_add(until_ms - now_ms, carrier_wait_timeout, self);
+    until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_msec);
+    priv->carrier_wait_source =
+        nm_g_timeout_add_source(until_ms - now_ms, carrier_wait_timeout, self);
 }
 
 gboolean
@@ -14563,7 +14816,15 @@ _set_unmanaged_flags(NMDevice           *self,
         new_state = was_managed ? NM_DEVICE_STATE_UNMANAGED : NM_DEVICE_STATE_UNAVAILABLE;
         if (new_state == NM_DEVICE_STATE_UNMANAGED) {
             _cancel_activation(self);
+        } else {
+            /* The assume check should happen before the device transitions to
+            * UNAVAILABLE, because in UNAVAILABLE we already clean up the IP
+            * configuration. Therefore, this function should never trigger a
+            * sync state transition.
+            */
+            nm_device_queue_recheck_assume(self);
         }
+
         if (now)
             nm_device_state_changed(self, new_state, reason);
         else
@@ -14629,11 +14890,11 @@ nm_device_check_unrealized_device_managed(NMDevice *self)
 
     nm_assert(!nm_device_is_real(self));
 
-    if (!nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA,
-                                                  NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
-                                                  self,
-                                                  TRUE,
-                                                  TRUE))
+    if (!nm_config_data_get_device_config_boolean_by_device(NM_CONFIG_GET_DATA,
+                                                            NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
+                                                            self,
+                                                            TRUE,
+                                                            TRUE))
         return FALSE;
 
     if (nm_device_spec_match_list(self, nm_settings_get_unmanaged_specs(priv->settings)))
@@ -14700,11 +14961,11 @@ nm_device_set_unmanaged_by_user_conf(NMDevice *self)
     gboolean      value;
     NMUnmanFlagOp set_op;
 
-    value = nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA,
-                                                     NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
-                                                     self,
-                                                     -1,
-                                                     TRUE);
+    value = nm_config_data_get_device_config_boolean_by_device(NM_CONFIG_GET_DATA,
+                                                               NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
+                                                               self,
+                                                               -1,
+                                                               TRUE);
     switch (value) {
     case TRUE:
         set_op = NM_UNMAN_FLAG_OP_SET_MANAGED;
@@ -14739,7 +15000,7 @@ nm_device_set_unmanaged_by_quitting(NMDevice *self)
 
     nm_device_set_unmanaged_by_flags(self,
                                      NM_UNMANAGED_QUITTING,
-                                     TRUE,
+                                     NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                      need_deactivate ? NM_DEVICE_STATE_REASON_REMOVED
                                                      : NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
 }
@@ -14918,7 +15179,10 @@ _nm_device_check_connection_available(NMDevice                      *self,
     /* an unrealized software device is always available, hardware devices never. */
     if (!nm_device_is_real(self)) {
         if (nm_device_is_software(self)) {
-            if (!nm_device_check_connection_compatible(self, connection, error ? &local : NULL)) {
+            if (!nm_device_check_connection_compatible(self,
+                                                       connection,
+                                                       TRUE,
+                                                       error ? &local : NULL)) {
                 if (error) {
                     g_return_val_if_fail(local, FALSE);
                     nm_utils_error_set(error,
@@ -14982,7 +15246,7 @@ _nm_device_check_connection_available(NMDevice                      *self,
         }
     }
 
-    if (!nm_device_check_connection_compatible(self, connection, error ? &local : NULL)) {
+    if (!nm_device_check_connection_compatible(self, connection, TRUE, error ? &local : NULL)) {
         if (error) {
             nm_utils_error_set(error,
                                local->domain == NM_UTILS_ERROR ? local->code
@@ -15084,14 +15348,11 @@ check_connection_available(NMDevice                      *self,
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    /* Connections which require a network connection are not available when
-     * the device has no carrier, even with ignore-carrer=TRUE.
-     */
-    if (priv->carrier || !connection_requires_carrier(connection))
+    if (priv->carrier)
         return TRUE;
 
     if (NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER)
-        && priv->carrier_wait_id != 0) {
+        && priv->carrier_wait_source) {
         /* The device has no carrier though the connection requires it.
          *
          * If we are still waiting for carrier, the connection is available
@@ -15099,12 +15360,6 @@ check_connection_available(NMDevice                      *self,
         return TRUE;
     }
 
-    /* master types are always available even without carrier.
-     * Making connection non-available would un-enslave slaves which
-     * is not desired. */
-    if (nm_device_is_master(self))
-        return TRUE;
-
     if (!priv->up) {
         /* If the device is !IFF_UP it also has no carrier. But we assume that if we
          * would start activating the device (and thereby set the device IFF_UP),
@@ -15114,6 +15369,18 @@ check_connection_available(NMDevice                      *self,
         return TRUE;
     }
 
+    if (!connection_requires_carrier(connection)) {
+        /* Connections that don't require carrier are available. */
+        return TRUE;
+    }
+
+    if (nm_device_is_master(self)) {
+        /* master types are always available even without carrier.
+         * Making connection non-available would un-enslave slaves which
+         * is not desired. */
+        return TRUE;
+    }
+
     nm_utils_error_set_literal(error,
                                NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
                                "device has no carrier");
@@ -15479,7 +15746,7 @@ _cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
 }
 
 static void
-_cleanup_generic_post(NMDevice *self, CleanupType cleanup_type)
+_cleanup_generic_post(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanup_type)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
@@ -15502,7 +15769,11 @@ _cleanup_generic_post(NMDevice *self, CleanupType cleanup_type)
         act_request_set(self, NULL);
     }
 
-    if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
+    if (cleanup_type == CLEANUP_TYPE_DECONFIGURE
+        && ((reason == NM_DEVICE_STATE_REASON_CARRIER && nm_device_is_master(self))
+            || !NM_IN_SET(reason,
+                          NM_DEVICE_STATE_REASON_NOW_MANAGED,
+                          NM_DEVICE_STATE_REASON_CARRIER))) {
         /* Check if the device was deactivated, and if so, delete_link.
          * Don't call delete_link synchronously because we are currently
          * handling a state change -- which is not reentrant. */
@@ -15623,8 +15894,8 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
                   ifindex);
             if (priv->mtu_initial) {
                 nm_platform_link_set_mtu(nm_device_get_platform(self), ifindex, priv->mtu_initial);
-                priv->carrier_wait_until_ms =
-                    nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS;
+                priv->carrier_wait_until_msec =
+                    nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MSEC;
             }
             if (priv->ip6_mtu_initial) {
                 char sbuf[64];
@@ -15641,6 +15912,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
     }
 
     _ethtool_state_reset(self);
+    link_properties_reset(self);
 
     if (priv->promisc_reset != NM_OPTION_BOOL_DEFAULT && ifindex > 0) {
         nm_platform_link_change_flags(nm_device_get_platform(self),
@@ -15650,7 +15922,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
         priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
     }
 
-    _cleanup_generic_post(self, cleanup_type);
+    _cleanup_generic_post(self, reason, cleanup_type);
 }
 
 static void
@@ -15675,6 +15947,9 @@ deactivate_ready(NMDevice *self, NMDeviceStateReason reason)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
+    if (priv->port_detach_count > 0)
+        return;
+
     if (priv->dispatcher.call_id)
         return;
 
@@ -15966,8 +16241,11 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
              * userspace IPv6LL enabled.
              */
             _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
+            if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_REMOVED) {
+                nm_device_cleanup(self, reason, CLEANUP_TYPE_REMOVED);
+            } else
+                nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE);
 
-            nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE);
         } else if (old_state < NM_DEVICE_STATE_DISCONNECTED) {
             if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED) {
                 /* Ensure IPv6 is set up as it may not have been done when
@@ -16036,7 +16314,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
 
         /* We cache the ignore_carrier state to not react on config-reloads while the connection
          * is active. But on deactivating, reset the ignore-carrier flag to the current state. */
-        priv->ignore_carrier = nm_config_data_get_ignore_carrier(NM_CONFIG_GET_DATA, self);
+        priv->ignore_carrier =
+            nm_config_data_get_ignore_carrier_by_device(NM_CONFIG_GET_DATA, self);
 
         if (quitting) {
             nm_dispatcher_call_device_sync(NM_DISPATCHER_ACTION_PRE_DOWN, self, req);
@@ -16743,7 +17022,7 @@ nm_device_hw_addr_set(NMDevice *self, const char *addr, const char *detail, gboo
  * @hwaddr: (out): the cloned MAC address to set on interface
  * @hwaddr_type: (out): the type of address to set
  * @hwaddr_detail: (out): the detail (origin) of address to set
- * @error: (out): on return, an error or %NULL
+ * @error: on return, an error or %NULL
  *
  * Computes the MAC to be set on a interface. On success, one of the
  * following exclusive conditions are verified:
@@ -16940,6 +17219,7 @@ nm_device_hw_addr_reset(NMDevice *self, const char *detail)
 {
     NMDevicePrivate *priv;
     const char      *addr;
+    int              ifindex;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
 
@@ -16949,7 +17229,13 @@ nm_device_hw_addr_reset(NMDevice *self, const char *detail)
         return TRUE;
 
     priv->hw_addr_type = HW_ADDR_TYPE_UNSET;
-    addr               = nm_device_get_initial_hw_address(self);
+
+    ifindex = nm_device_get_ip_ifindex(self);
+    if (ifindex <= 0) {
+        return TRUE;
+    }
+
+    addr = nm_device_get_initial_hw_address(self);
     if (!addr) {
         /* as hw_addr_type is not UNSET, we expect that we can get an
          * initial address to which to reset. */
@@ -17028,38 +17314,11 @@ nm_device_spec_match_list(NMDevice *self, const GSList *specs)
 int
 nm_device_spec_match_list_full(NMDevice *self, const GSList *specs, int no_match_value)
 {
-    NMDeviceClass       *klass;
-    NMMatchSpecMatchType m;
-    const char          *hw_address = NULL;
-    gboolean             is_fake;
-
-    g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
+    NMMatchSpecDeviceData data;
+    NMMatchSpecMatchType  m;
 
-    klass      = NM_DEVICE_GET_CLASS(self);
-    hw_address = nm_device_get_permanent_hw_address_full(
-        self,
-        !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT),
-        &is_fake);
-
-    m = nm_match_spec_device(specs,
-                             nm_device_get_iface(self),
-                             nm_device_get_type_description(self),
-                             nm_device_get_driver(self),
-                             nm_device_get_driver_version(self),
-                             is_fake ? NULL : hw_address,
-                             klass->get_s390_subchannels ? klass->get_s390_subchannels(self) : NULL,
-                             nm_dhcp_manager_get_config(nm_dhcp_manager_get()));
-
-    switch (m) {
-    case NM_MATCH_SPEC_MATCH:
-        return TRUE;
-    case NM_MATCH_SPEC_NEG_MATCH:
-        return FALSE;
-    case NM_MATCH_SPEC_NO_MATCH:
-        return no_match_value;
-    }
-    nm_assert_not_reached();
-    return no_match_value;
+    m = nm_match_spec_device(specs, nm_match_spec_device_data_init_from_device(&data, self));
+    return nm_match_spec_match_type_to_bool(m, no_match_value);
 }
 
 guint
@@ -17747,6 +18006,8 @@ nm_device_init(NMDevice *self)
 
     c_list_init(&priv->concheck_lst_head);
     c_list_init(&self->devices_lst);
+    c_list_init(&self->devcon_dev_lst_head);
+    c_list_init(&self->policy_auto_activate_lst);
     c_list_init(&priv->slaves);
 
     priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_NONE;
@@ -17773,7 +18034,11 @@ nm_device_init(NMDevice *self)
     priv->unmanaged_mask        = priv->unmanaged_flags;
     priv->available_connections = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL);
     priv->ip6_saved_properties  = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free);
-    priv->sys_iface_state_      = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
+
+    priv->sys_iface_state_ = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
+    /* If networking is already disabled at boot, we want to manage all devices
+     * after re-enabling networking; hence, the initial state is MANAGED. */
+    priv->sys_iface_state_before_sleep = NM_DEVICE_SYS_IFACE_STATE_MANAGED;
 
     priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
 }
@@ -17867,6 +18132,9 @@ dispose(GObject *object)
     _LOGD(LOGD_DEVICE, "disposing");
 
     nm_assert(c_list_is_empty(&self->devices_lst));
+    nm_assert(c_list_is_empty(&self->devcon_dev_lst_head));
+    nm_assert(c_list_is_empty(&self->policy_auto_activate_lst));
+    nm_assert(!self->policy_auto_activate_idle_source);
 
     while ((con_handle = c_list_first_entry(&priv->concheck_lst_head,
                                             NMDeviceConnectivityHandle,
@@ -17899,7 +18167,7 @@ dispose(GObject *object)
     /* Let the kernel manage IPv6LL again */
     _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_EUI64);
 
-    _cleanup_generic_post(self, CLEANUP_TYPE_KEEP);
+    _cleanup_generic_post(self, NM_DEVICE_STATE_REASON_NONE, CLEANUP_TYPE_KEEP);
 
     nm_assert(priv->master_ready_id == 0);
 
@@ -17925,7 +18193,7 @@ dispose(GObject *object)
 
     available_connections_del_all(self);
 
-    if (nm_clear_g_source(&priv->carrier_wait_id))
+    if (nm_clear_g_source_inst(&priv->carrier_wait_source))
         nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
 
     _clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
@@ -18529,16 +18797,6 @@ nm_device_class_init(NMDeviceClass *klass)
                                     G_TYPE_NONE,
                                     0);
 
-    signals[RECHECK_AUTO_ACTIVATE] = g_signal_new(NM_DEVICE_RECHECK_AUTO_ACTIVATE,
-                                                  G_OBJECT_CLASS_TYPE(object_class),
-                                                  G_SIGNAL_RUN_FIRST,
-                                                  0,
-                                                  NULL,
-                                                  NULL,
-                                                  NULL,
-                                                  G_TYPE_NONE,
-                                                  0);
-
     signals[RECHECK_ASSUME] = g_signal_new(NM_DEVICE_RECHECK_ASSUME,
                                            G_OBJECT_CLASS_TYPE(object_class),
                                            G_SIGNAL_RUN_FIRST,
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index bcf4d7b9..b096d23a 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -75,7 +75,6 @@
 #define NM_DEVICE_IP6_PREFIX_DELEGATED     "ip6-prefix-delegated"
 #define NM_DEVICE_IP6_SUBNET_NEEDED        "ip6-subnet-needed"
 #define NM_DEVICE_REMOVED                  "removed"
-#define NM_DEVICE_RECHECK_AUTO_ACTIVATE    "recheck-auto-activate"
 #define NM_DEVICE_RECHECK_ASSUME           "recheck-assume"
 #define NM_DEVICE_STATE_CHANGED            "state-changed"
 #define NM_DEVICE_LINK_INITIALIZED         "link-initialized"
@@ -144,6 +143,10 @@ struct _NMDevice {
     NMDBusObject             parent;
     struct _NMDevicePrivate *_priv;
     CList                    devices_lst;
+    CList                    devcon_dev_lst_head;
+
+    CList    policy_auto_activate_lst;
+    GSource *policy_auto_activate_idle_source;
 };
 
 /* The flags have an relaxing meaning, that means, specifying more flags, can make
@@ -294,7 +297,7 @@ typedef struct _NMDeviceClass {
     GPtrArray *(*get_extra_rules)(NMDevice *self);
 
     /* allow derived classes to override the result of nm_device_autoconnect_allowed().
-     * If the value changes, the class should call nm_device_emit_recheck_auto_activate(),
+     * If the value changes, the class should call nm_device_recheck_auto_activate_schedule(),
      * which emits NM_DEVICE_RECHECK_AUTO_ACTIVATE signal. */
     gboolean (*get_autoconnect_allowed)(NMDevice *self);
 
@@ -321,6 +324,7 @@ typedef struct _NMDeviceClass {
      */
     gboolean (*check_connection_compatible)(NMDevice     *self,
                                             NMConnection *connection,
+                                            gboolean      check_properties,
                                             GError      **error);
 
     /* Checks whether the connection is likely available to be activated,
@@ -387,7 +391,15 @@ typedef struct _NMDeviceClass {
                              GCancellable              *cancellable,
                              NMDeviceAttachPortCallback callback,
                              gpointer                   user_data);
-    void (*detach_port)(NMDevice *self, NMDevice *port, gboolean configure);
+    /* This works similarly to attach_port(). However, current
+     * implementations don't report errors and so the only possible
+     * return values are TRUE and DEFAULT. */
+    NMTernary (*detach_port)(NMDevice                  *self,
+                             NMDevice                  *port,
+                             gboolean                   configure,
+                             GCancellable              *cancellable,
+                             NMDeviceAttachPortCallback callback,
+                             gpointer                   user_data);
 
     void (*parent_changed_notify)(NMDevice *self,
                                   int       old_ifindex,
@@ -421,6 +433,10 @@ typedef struct _NMDeviceClass {
     const char *(*get_dhcp_anycast_address)(NMDevice *self);
 } NMDeviceClass;
 
+NMSettings *nm_device_get_settings(NMDevice *self);
+
+NMManager *nm_device_get_manager(NMDevice *self);
+
 GType nm_device_get_type(void);
 
 struct _NMDedupMultiIndex *nm_device_get_multi_index(NMDevice *self);
@@ -444,9 +460,11 @@ gboolean     nm_device_is_real(NMDevice *dev);
 const char  *nm_device_get_ip_iface(NMDevice *dev);
 const char  *nm_device_get_ip_iface_from_platform(NMDevice *dev);
 int          nm_device_get_ip_ifindex(const NMDevice *dev);
+const char  *nm_device_get_s390_subchannels(NMDevice *self);
 const char  *nm_device_get_driver(NMDevice *dev);
 const char  *nm_device_get_driver_version(NMDevice *dev);
 const char  *nm_device_get_type_desc(NMDevice *dev);
+const char  *nm_device_get_type_desc_for_log(NMDevice *dev);
 const char  *nm_device_get_type_description(NMDevice *dev);
 NMDeviceType nm_device_get_device_type(NMDevice *dev);
 NMLinkType   nm_device_get_link_type(NMDevice *dev);
@@ -526,8 +544,10 @@ gboolean nm_device_complete_connection(NMDevice            *device,
                                        NMConnection *const *existing_connections,
                                        GError             **error);
 
-gboolean
-nm_device_check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error);
+gboolean nm_device_check_connection_compatible(NMDevice     *device,
+                                               NMConnection *connection,
+                                               gboolean      check_properties,
+                                               GError      **error);
 
 gboolean nm_device_check_slave_connection_compatible(NMDevice *device, NMConnection *connection);
 
@@ -608,9 +628,9 @@ typedef enum {
 } NMUnmanagedFlags;
 
 typedef enum {
-    NM_UNMAN_FLAG_OP_SET_MANAGED   = FALSE,
-    NM_UNMAN_FLAG_OP_SET_UNMANAGED = TRUE,
-    NM_UNMAN_FLAG_OP_FORGET        = 2,
+    NM_UNMAN_FLAG_OP_SET_MANAGED = 0,
+    NM_UNMAN_FLAG_OP_SET_UNMANAGED,
+    NM_UNMAN_FLAG_OP_FORGET,
 } NMUnmanFlagOp;
 
 const char *nm_unmanaged_flags2str(NMUnmanagedFlags flags, char *buf, gsize len);
@@ -631,6 +651,7 @@ void nm_device_set_unmanaged_by_user_settings(NMDevice *self, gboolean now);
 void nm_device_set_unmanaged_by_user_udev(NMDevice *self);
 void nm_device_set_unmanaged_by_user_conf(NMDevice *self);
 void nm_device_set_unmanaged_by_quitting(NMDevice *device);
+NMDeviceStateReason nm_device_get_manage_reason_external(NMDevice *self);
 
 gboolean nm_device_check_unrealized_device_managed(NMDevice *self);
 
@@ -701,7 +722,7 @@ nm_device_autoconnect_blocked_unset(NMDevice *device, NMDeviceAutoconnectBlocked
     nm_device_autoconnect_blocked_set_full(device, mask, NM_DEVICE_AUTOCONNECT_BLOCKED_NONE);
 }
 
-void nm_device_emit_recheck_auto_activate(NMDevice *device);
+void nm_device_recheck_auto_activate_schedule(NMDevice *device);
 
 NMDeviceSysIfaceState nm_device_sys_iface_state_get(NMDevice *device);
 
@@ -710,6 +731,10 @@ gboolean nm_device_sys_iface_state_is_external_or_assume(NMDevice *self);
 
 void nm_device_sys_iface_state_set(NMDevice *device, NMDeviceSysIfaceState sys_iface_state);
 
+void nm_device_notify_sleeping(NMDevice *self);
+
+NMDeviceSysIfaceState nm_device_get_sys_iface_state_before_sleep(NMDevice *self);
+
 void nm_device_state_changed(NMDevice *device, NMDeviceState state, NMDeviceStateReason reason);
 
 void nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason);
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
index 7b319af3..ff1917b1 100644
--- a/src/core/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -97,9 +97,16 @@ attach_port(NMDevice                  *device,
     return TRUE;
 }
 
-static void
-detach_port(NMDevice *device, NMDevice *port, gboolean configure)
-{}
+static NMTernary
+detach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
+{
+    return TRUE;
+}
 
 void
 nm_device_ovs_reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new)
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index 711f65cb..fd48c2fd 100644
--- a/src/core/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -91,12 +91,15 @@ can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **speci
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMSettingOvsInterface *s_ovs_iface;
 
     if (!NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_ovs_iface = nm_connection_get_setting_ovs_interface(connection);
@@ -132,6 +135,8 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
             nm_device_devip_set_failed(device, AF_INET6, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
             return;
         }
+
+        nm_device_link_properties_set(device, FALSE);
         nm_device_bring_up(device);
 
         nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
@@ -214,6 +219,7 @@ _set_ip_ifindex_tun(gpointer user_data)
 
     priv->wait_link_is_waiting = FALSE;
     nm_device_set_ip_ifindex(device, priv->wait_link_ifindex);
+    nm_device_link_properties_set(device, FALSE);
 
     nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
     nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
@@ -303,6 +309,7 @@ act_stage3_ip_config(NMDevice *device, int addr_family)
         return;
     }
 
+    nm_device_link_properties_set(device, FALSE);
     nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
@@ -479,7 +486,7 @@ ovsdb_ready(NMOvsdb *ovsdb, NMDeviceOvsInterface *self)
                                       NM_DEVICE_STATE_REASON_NONE,
                                       NM_DEVICE_STATE_REASON_NONE);
     nm_device_recheck_available_connections(device);
-    nm_device_emit_recheck_auto_activate(device);
+    nm_device_recheck_auto_activate_schedule(device);
 }
 
 static void
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index 5510e39f..5ede46e9 100644
--- a/src/core/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -78,10 +78,11 @@ typedef struct {
     GCancellable              *cancellable;
     NMDeviceAttachPortCallback callback;
     gpointer                   callback_user_data;
+    gboolean                   add;
 } AttachPortData;
 
 static void
-add_iface_cb(GError *error, gpointer user_data)
+add_del_iface_cb(GError *error, gpointer user_data)
 {
     AttachPortData       *data = user_data;
     NMDeviceOvsPort      *self;
@@ -93,15 +94,17 @@ add_iface_cb(GError *error, gpointer user_data)
     } else if (error && !nm_utils_error_is_cancelled_or_disposing(error)) {
         self = NM_DEVICE_OVS_PORT(data->device);
         _LOGW(LOGD_DEVICE,
-              "device %s could not be added to a ovs port: %s",
+              "device %s could not be %s a ovs port: %s",
               nm_device_get_iface(data->port),
+              data->add ? "added to" : "removed from",
               error->message);
         nm_device_state_changed(data->port,
                                 NM_DEVICE_STATE_FAILED,
                                 NM_DEVICE_STATE_REASON_OVSDB_FAILED);
     }
 
-    data->callback(data->device, error, data->callback_user_data);
+    if (data->callback)
+        data->callback(data->device, error, data->callback_user_data);
 
     g_object_unref(data->device);
     g_object_unref(data->port);
@@ -178,6 +181,7 @@ attach_port(NMDevice                  *device,
         .cancellable        = g_object_ref(cancellable),
         .callback           = callback,
         .callback_user_data = user_data,
+        .add                = TRUE,
     };
 
     nm_ovsdb_add_interface(nm_ovsdb_get(),
@@ -186,7 +190,7 @@ attach_port(NMDevice                  *device,
                            nm_device_get_applied_connection(port),
                            bridge_device,
                            port,
-                           add_iface_cb,
+                           add_del_iface_cb,
                            data);
 
     /* DPDK ports does not have a link after the devbind, so the MTU must be
@@ -205,29 +209,19 @@ attach_port(NMDevice                  *device,
     return NM_TERNARY_DEFAULT;
 }
 
-static void
-del_iface_cb(GError *error, gpointer user_data)
-{
-    NMDevice *slave = user_data;
-
-    if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
-        nm_log_warn(LOGD_DEVICE,
-                    "device %s could not be removed from a ovs port: %s",
-                    nm_device_get_iface(slave),
-                    error->message);
-        nm_device_state_changed(slave, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_OVSDB_FAILED);
-    }
-
-    g_object_unref(slave);
-}
-
-static void
-detach_port(NMDevice *device, NMDevice *port, gboolean configure)
+static NMTernary
+detach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     NMDeviceOvsPort *self             = NM_DEVICE_OVS_PORT(device);
     bool             port_not_managed = !NM_IN_SET(nm_device_sys_iface_state_get(port),
                                        NM_DEVICE_SYS_IFACE_STATE_MANAGED,
                                        NM_DEVICE_SYS_IFACE_STATE_ASSUME);
+    NMTernary        ret              = TRUE;
 
     _LOGI(LOGD_DEVICE, "detaching ovs interface %s", nm_device_get_ip_iface(port));
 
@@ -236,10 +230,20 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure)
      * to make sure its OVSDB entry is gone.
      */
     if (configure || port_not_managed) {
-        nm_ovsdb_del_interface(nm_ovsdb_get(),
-                               nm_device_get_iface(port),
-                               del_iface_cb,
-                               g_object_ref(port));
+        AttachPortData *data;
+
+        data  = g_slice_new(AttachPortData);
+        *data = (AttachPortData){
+            .device             = g_object_ref(device),
+            .port               = g_object_ref(port),
+            .cancellable        = nm_g_object_ref(cancellable),
+            .callback           = callback,
+            .callback_user_data = user_data,
+            .add                = FALSE,
+        };
+
+        nm_ovsdb_del_interface(nm_ovsdb_get(), nm_device_get_iface(port), add_del_iface_cb, data);
+        ret = NM_TERNARY_DEFAULT;
     }
 
     if (configure) {
@@ -248,6 +252,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure)
         if (NM_IS_DEVICE_OVS_INTERFACE(port))
             nm_device_update_from_platform_link(port, NULL);
     }
+
+    return ret;
 }
 
 /*****************************************************************************/
diff --git a/src/core/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c
index 50023778..2ca1a0b5 100644
--- a/src/core/devices/ovs/nm-ovs-factory.c
+++ b/src/core/devices/ovs/nm-ovs-factory.c
@@ -240,9 +240,11 @@ ovsdb_interface_failed(NMOvsdb         *ovsdb,
         return;
 
     if (connection) {
-        nm_settings_connection_autoconnect_blocked_reason_set(
+        nm_manager_devcon_autoconnect_blocked_reason_set(
+            nm_device_get_manager(device),
+            device,
             connection,
-            NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+            NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
             TRUE);
     }
 
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index 85b7953f..68366f73 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -12,6 +12,7 @@
 
 #include "libnm-glib-aux/nm-jansson.h"
 #include "libnm-glib-aux/nm-str-buf.h"
+#include "libnm-glib-aux/nm-io-utils.h"
 #include "nm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "devices/nm-device.h"
@@ -134,14 +135,18 @@ enum {
 static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
-    NMPlatform        *platform;
-    GSocketConnection *conn;
-    GCancellable      *conn_cancellable;
-    char               buf[4096]; /* Input buffer */
-    size_t             bufp;      /* Last decoded byte in the input buffer. */
-    GString           *input;     /* JSON stream waiting for decoding. */
-    GString           *output;    /* JSON stream to be sent. */
-    guint64            call_id_counter;
+    NMPlatform   *platform;
+    int           conn_fd;
+    GSource      *conn_fd_in_source;
+    GSource      *conn_fd_out_source;
+    GCancellable *conn_cancellable;
+
+    NMStrBuf input_buf;
+    NMStrBuf output_buf;
+
+    GSource *input_timeout_source;
+
+    guint64 call_id_counter;
 
     CList calls_lst_head;
 
@@ -177,12 +182,13 @@ NM_DEFINE_SINGLETON_GETTER(NMOvsdb, nm_ovsdb_get, NM_TYPE_OVSDB);
 
 /*****************************************************************************/
 
-static void ovsdb_try_connect(NMOvsdb *self);
-static void ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing);
-static void ovsdb_read(NMOvsdb *self);
-static void ovsdb_write(NMOvsdb *self);
-static void ovsdb_next_command(NMOvsdb *self);
-static void cleanup_check_ready(NMOvsdb *self);
+static void     ovsdb_try_connect(NMOvsdb *self);
+static void     ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing);
+static void     ovsdb_read(NMOvsdb *self);
+static void     ovsdb_write_try(NMOvsdb *self);
+static gboolean ovsdb_write_cb(int fd, GIOCondition condition, gpointer user_data);
+static void     ovsdb_next_command(NMOvsdb *self);
+static void     cleanup_check_ready(NMOvsdb *self);
 
 /*****************************************************************************/
 
@@ -1446,10 +1452,10 @@ ovsdb_next_command(NMOvsdb *self)
 {
     NMOvsdbPrivate             *priv = NM_OVSDB_GET_PRIVATE(self);
     OvsdbMethodCall            *call;
-    char                       *cmd;
+    nm_auto_free char          *cmd = NULL;
     nm_auto_decref_json json_t *msg = NULL;
 
-    if (!priv->conn)
+    if (priv->conn_fd < 0)
         return;
 
     if (c_list_is_empty(&priv->calls_lst_head))
@@ -1586,10 +1592,9 @@ ovsdb_next_command(NMOvsdb *self)
 
     cmd = json_dumps(msg, 0);
     _LOGT_call(call, "send: call-id=%" G_GUINT64_FORMAT ", %s", call->call_id, cmd);
-    g_string_append(priv->output, cmd);
-    free(cmd);
+    nm_str_buf_append(&priv->output_buf, cmd);
 
-    ovsdb_write(self);
+    ovsdb_write_try(self);
 }
 
 /**
@@ -2188,20 +2193,18 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
 static void
 ovsdb_got_echo(NMOvsdb *self, json_int_t id, json_t *data)
 {
-    NMOvsdbPrivate             *priv = NM_OVSDB_GET_PRIVATE(self);
-    nm_auto_decref_json json_t *msg  = NULL;
-    char                       *reply;
-    gboolean                    output_was_empty;
-
-    output_was_empty = priv->output->len == 0;
+    NMOvsdbPrivate             *priv  = NM_OVSDB_GET_PRIVATE(self);
+    nm_auto_decref_json json_t *msg   = NULL;
+    nm_auto_free char          *reply = NULL;
 
     msg   = json_pack("{s:I, s:O}", "id", id, "result", data);
     reply = json_dumps(msg, 0);
-    g_string_append(priv->output, reply);
-    free(reply);
 
-    if (output_was_empty)
-        ovsdb_write(self);
+    _LOGT("send: echo: %s", reply);
+
+    nm_str_buf_append(&priv->output_buf, reply);
+
+    ovsdb_write_try(self);
 }
 
 /**
@@ -2274,13 +2277,13 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
 
         /* This is a response to a method call. */
         if (c_list_is_empty(&priv->calls_lst_head)) {
-            _LOGE("there are no queued calls expecting response %" G_GUINT64_FORMAT, (guint64) id);
+            _LOGW("there are no queued calls expecting response %" G_GUINT64_FORMAT, (guint64) id);
             ovsdb_disconnect(self, FALSE, FALSE);
             return;
         }
         call = c_list_first_entry(&priv->calls_lst_head, OvsdbMethodCall, calls_lst);
         if (call->call_id != id) {
-            _LOGE("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT,
+            _LOGW("expected a response to call %" G_GUINT64_FORMAT ", not %" G_GUINT64_FORMAT,
                   call->call_id,
                   (guint64) id);
             ovsdb_disconnect(self, FALSE, FALSE);
@@ -2305,7 +2308,7 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
 
         /* Don't progress further commands in case the callback hit an error
          * and disconnected us. */
-        if (!priv->conn)
+        if (priv->conn_fd < 0)
             return;
 
         /* Now we're free to serialize and send the next command, if any. */
@@ -2320,138 +2323,197 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
 
 /*****************************************************************************/
 
+typedef struct {
+    gsize     bufp;
+    NMStrBuf *input;
+} JsonReadMsgData;
+
 /* Lower level marshalling and demarshalling of the JSON-RPC traffic on the
  * ovsdb socket. */
 
 static size_t
-_json_callback(void *buffer, size_t buflen, void *user_data)
+_json_read_msg_cb(void *buffer, size_t buflen, void *user_data)
 {
-    NMOvsdb        *self = NM_OVSDB(user_data);
-    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+    JsonReadMsgData *data = user_data;
+
+    nm_assert(buffer);
+    nm_assert(buflen > 0);
 
-    if (priv->bufp == priv->input->len) {
+    if (data->bufp == data->input->len) {
         /* No more bytes buffered for decoding. */
         return 0;
     }
 
     /* Pass one more byte to the JSON decoder. */
-    *(char *) buffer = priv->input->str[priv->bufp];
-    priv->bufp++;
-
-    return (size_t) 1;
+    *(char *) buffer = nm_str_buf_get_char(data->input, data->bufp);
+    data->bufp++;
+    return 1;
 }
 
-/**
- * ovsdb_read_cb:
- *
- * Read out the data available from the ovsdb socket and try to deserialize
- * the JSON. If we see a complete object, pass it upwards to ovsdb_got_msg().
- */
-static void
-ovsdb_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+static json_t *
+_json_read_msg(NMOvsdb *self, NMStrBuf *input)
 {
-    NMOvsdb        *self   = NM_OVSDB(user_data);
-    NMOvsdbPrivate *priv   = NM_OVSDB_GET_PRIVATE(self);
-    GInputStream   *stream = G_INPUT_STREAM(source_object);
-    GError         *error  = NULL;
-    gssize          size;
-    json_t         *msg;
-    json_error_t    json_error = {
+    gs_free char   *ss   = NULL;
+    JsonReadMsgData data = {
+        .bufp  = 0,
+        .input = input,
+    };
+    json_error_t json_error = {
         0,
     };
+    json_t *msg;
 
-    size = g_input_stream_read_finish(stream, res, &error);
-    if (size == -1) {
-        /* ovsdb-server was possibly restarted */
-        _LOGW("short read from ovsdb: %s", error->message);
-        priv->num_failures++;
-        g_clear_error(&error);
-        ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
-        return;
-    }
+    /* The callback always eats up only up to a single byte. This makes it
+     * possible for us to identify complete JSON objects in spite of us not
+     * knowing the length in advance. */
+    msg = json_load_callback(_json_read_msg_cb, &data, JSON_DISABLE_EOF_CHECK, &json_error);
+    if (!msg)
+        return NULL;
 
-    g_string_append_len(priv->input, priv->buf, size);
-    do {
-        priv->bufp = 0;
-        /* The callback always eats up only up to a single byte. This makes
-         * it possible for us to identify complete JSON objects in spite of
-         * us not knowing the length in advance. */
-        msg = json_load_callback(_json_callback, self, JSON_DISABLE_EOF_CHECK, &json_error);
-        if (msg) {
-            ovsdb_got_msg(self, msg);
-            g_string_erase(priv->input, 0, priv->bufp);
-        }
-        json_decref(msg);
-    } while (msg);
+    nm_assert(data.bufp > 0);
 
-    if (!priv->conn)
-        return;
+    _LOGT("json: parse %zu bytes: \"%s\"",
+          data.bufp,
+          (ss = g_strndup(nm_str_buf_get_str_at_unsafe(input, 0), data.bufp)));
 
-    if (size)
-        ovsdb_read(self);
+    nm_str_buf_erase(input, 0, data.bufp, FALSE);
+    return msg;
 }
 
-static void
-ovsdb_read(NMOvsdb *self)
+static gboolean
+_ovsdb_read_input_timeout_cb(gpointer user_data)
 {
+    NMOvsdb        *self = user_data;
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
 
-    g_input_stream_read_async(g_io_stream_get_input_stream(G_IO_STREAM(priv->conn)),
-                              priv->buf,
-                              sizeof(priv->buf),
-                              G_PRIORITY_DEFAULT,
-                              NULL,
-                              ovsdb_read_cb,
-                              self);
+    _LOGW("invalid/incomplete data in receive buffer. Reset");
+    priv->num_failures++;
+    ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
+    return G_SOURCE_CONTINUE;
 }
 
 static void
-ovsdb_write_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+ovsdb_read(NMOvsdb *self)
 {
-    GOutputStream  *stream = G_OUTPUT_STREAM(source_object);
-    NMOvsdb        *self   = NM_OVSDB(user_data);
-    NMOvsdbPrivate *priv   = NM_OVSDB_GET_PRIVATE(self);
-    GError         *error  = NULL;
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
     gssize          size;
 
-    size = g_output_stream_write_finish(stream, res, &error);
-    if (size == -1) {
+again:
+    size = nm_utils_fd_read(priv->conn_fd, &priv->input_buf);
+
+    if (size <= 0) {
+        if (size == -EAGAIN) {
+            if (priv->input_buf.len == 0)
+                nm_clear_g_source_inst(&priv->input_timeout_source);
+            else if (!priv->input_timeout_source) {
+                /* We have data in the buffer but nothing further to read. Schedule a timer,
+                 * if we don't get the rest within timeout, it means that the buffer
+                 * content is broken (_json_read_msg() cannot extract any data) and
+                 * we disconnect. */
+                priv->input_timeout_source =
+                    nm_g_timeout_add_seconds_source(5, _ovsdb_read_input_timeout_cb, NULL);
+            }
+            return;
+        }
+
         /* ovsdb-server was possibly restarted */
-        _LOGW("short write to ovsdb: %s", error->message);
+        _LOGW("short read from ovsdb: %s", nm_strerror_native(-size));
         priv->num_failures++;
-        g_clear_error(&error);
         ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
         return;
     }
 
-    if (!priv->conn)
-        return;
+    nm_assert(priv->input_buf.len > 0);
+
+    while (TRUE) {
+        nm_auto_decref_json json_t *msg = NULL;
+
+        msg = _json_read_msg(self, &priv->input_buf);
+        if (!msg)
+            break;
 
-    g_string_erase(priv->output, 0, size);
+        nm_clear_g_source_inst(&priv->input_timeout_source);
+        ovsdb_got_msg(self, msg);
 
-    ovsdb_write(self);
+        if (priv->input_buf.len == 0)
+            break;
+    }
+
+    if (priv->input_buf.len > 0) {
+        if (priv->input_buf.len > 50 * 1024 * 1024) {
+            _LOGW("received too much data from ovsdb that is not valid JSON");
+            priv->num_failures++;
+            ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
+            return;
+        }
+        /* We have an incomplete message in the message buffer. Don't wait for another round
+         * of "poll", instead try to read it again. */
+        goto again;
+    }
+
+    nm_clear_g_source_inst(&priv->input_timeout_source);
+}
+
+static gboolean
+ovsdb_read_cb(int fd, GIOCondition condition, gpointer user_data)
+{
+    ovsdb_read(user_data);
+    return G_SOURCE_CONTINUE;
 }
 
 static void
 ovsdb_write(NMOvsdb *self)
 {
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
-    GOutputStream  *stream;
+    gssize          n;
 
-    if (!priv->output->len)
+again:
+    if (priv->output_buf.len == 0) {
+        nm_clear_g_source_inst(&priv->conn_fd_out_source);
         return;
+    }
+
+    n = write(priv->conn_fd,
+              nm_str_buf_get_str_at_unsafe(&priv->output_buf, 0),
+              priv->output_buf.len);
+
+    if (n < 0)
+        n = -NM_ERRNO_NATIVE(errno);
 
-    stream = g_io_stream_get_output_stream(G_IO_STREAM(priv->conn));
-    if (g_output_stream_has_pending(stream))
+    if (n == -EAGAIN) {
+        if (!priv->conn_fd_out_source) {
+            priv->conn_fd_out_source =
+                nm_g_unix_fd_add_source(priv->conn_fd, G_IO_OUT, ovsdb_write_cb, self);
+        }
         return;
+    }
+
+    if (n <= 0) {
+        /* ovsdb-server was possibly restarted */
+        _LOGW("short write to ovsdb: %s", nm_strerror_native(-n));
+        priv->num_failures++;
+        ovsdb_disconnect(self, priv->num_failures <= OVSDB_MAX_FAILURES, FALSE);
+        return;
+    }
 
-    g_output_stream_write_async(stream,
-                                priv->output->str,
-                                priv->output->len,
-                                G_PRIORITY_DEFAULT,
-                                NULL,
-                                ovsdb_write_cb,
-                                self);
+    nm_str_buf_erase(&priv->output_buf, 0, n, FALSE);
+    goto again;
+}
+
+static void
+ovsdb_write_try(NMOvsdb *self)
+{
+    NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
+
+    if (priv->conn_fd >= 0 && !priv->conn_fd_out_source)
+        ovsdb_write(self);
+}
+
+static gboolean
+ovsdb_write_cb(int fd, GIOCondition condition, gpointer user_data)
+{
+    ovsdb_write(user_data);
+    return G_SOURCE_CONTINUE;
 }
 
 /*****************************************************************************/
@@ -2474,7 +2536,7 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
 
     nm_assert(!retry || !is_disposing);
 
-    if (!priv->conn && !priv->conn_cancellable)
+    if (priv->conn_fd < 0 && !priv->conn_cancellable)
         return;
 
     _LOGD("disconnecting from ovsdb, retry %d", retry);
@@ -2498,10 +2560,12 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
             _call_complete(call, NULL, error);
     }
 
-    priv->bufp = 0;
-    g_string_truncate(priv->input, 0);
-    g_string_truncate(priv->output, 0);
-    g_clear_object(&priv->conn);
+    nm_str_buf_reset(&priv->input_buf);
+    nm_str_buf_reset(&priv->output_buf);
+    nm_clear_fd(&priv->conn_fd);
+    nm_clear_g_source_inst(&priv->conn_fd_in_source);
+    nm_clear_g_source_inst(&priv->conn_fd_out_source);
+    nm_clear_g_source_inst(&priv->input_timeout_source);
     nm_clear_g_free(&priv->db_uuid);
     nm_clear_g_cancellable(&priv->conn_cancellable);
 
@@ -2702,15 +2766,12 @@ _ovsdb_connect_complete_with_fd(NMOvsdb *self, int fd_take)
     gs_unref_object GSocket *socket = NULL;
     gs_free_error GError    *error  = NULL;
 
-    socket = g_socket_new_from_fd(nm_steal_fd(&fd_take), &error);
-    if (!socket) {
-        _LOGT("connect: failure to open socket for new FD: %s", error->message);
-        ovsdb_disconnect(self, FALSE, FALSE);
-        return;
-    }
+    nm_clear_g_cancellable(&priv->conn_cancellable);
+
+    nm_io_fcntl_setfl_update_nonblock(fd_take);
 
-    priv->conn = g_socket_connection_factory_create_connection(socket);
-    g_clear_object(&priv->conn_cancellable);
+    priv->conn_fd           = nm_steal_fd(&fd_take);
+    priv->conn_fd_in_source = nm_g_unix_fd_add_source(priv->conn_fd, G_IO_IN, ovsdb_read_cb, self);
 
     ovsdb_read(self);
     ovsdb_next_command(self);
@@ -2784,7 +2845,7 @@ ovsdb_try_connect(NMOvsdb *self)
 {
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
 
-    if (priv->conn || priv->conn_cancellable)
+    if (priv->conn_fd >= 0 || priv->conn_cancellable)
         return;
 
     _LOGT("connect: start connecting socket %s on idle", NM_OVSDB_SOCKET);
@@ -2964,11 +3025,15 @@ nm_ovsdb_init(NMOvsdb *self)
 {
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
 
+    priv->conn_fd = -1;
+
+    priv->input_buf  = NM_STR_BUF_INIT(0, FALSE);
+    priv->output_buf = NM_STR_BUF_INIT(0, FALSE);
+
     c_list_init(&priv->calls_lst_head);
 
     priv->platform = g_object_ref(NM_PLATFORM_GET);
-    priv->input    = g_string_new(NULL);
-    priv->output   = g_string_new(NULL);
+
     priv->bridges =
         g_hash_table_new_full(nm_pstr_hash, nm_pstr_equal, (GDestroyNotify) _free_bridge, NULL);
     priv->ports =
@@ -2989,14 +3054,8 @@ dispose(GObject *object)
 
     nm_assert(c_list_is_empty(&priv->calls_lst_head));
 
-    if (priv->input) {
-        g_string_free(priv->input, TRUE);
-        priv->input = NULL;
-    }
-    if (priv->output) {
-        g_string_free(priv->output, TRUE);
-        priv->output = NULL;
-    }
+    nm_str_buf_destroy(&priv->input_buf);
+    nm_str_buf_destroy(&priv->output_buf);
 
     g_clear_object(&priv->platform);
     nm_clear_pointer(&priv->bridges, g_hash_table_destroy);
diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c
index 5c955986..4d748362 100644
--- a/src/core/devices/team/nm-device-team.c
+++ b/src/core/devices/team/nm-device-team.c
@@ -899,8 +899,13 @@ attach_port(NMDevice                  *device,
     return TRUE;
 }
 
-static void
-detach_port(NMDevice *device, NMDevice *port, gboolean configure)
+static NMTernary
+detach_port(NMDevice                  *device,
+            NMDevice                  *port,
+            gboolean                   configure,
+            GCancellable              *cancellable,
+            NMDeviceAttachPortCallback callback,
+            gpointer                   user_data)
 {
     NMDeviceTeam        *self       = NM_DEVICE_TEAM(device);
     NMDeviceTeamPrivate *priv       = NM_DEVICE_TEAM_GET_PRIVATE(self);
@@ -950,6 +955,8 @@ detach_port(NMDevice *device, NMDevice *port, gboolean configure)
         _update_port_config(self, port_iface, "{}");
         g_hash_table_remove(priv->port_configs, port_iface);
     }
+
+    return TRUE;
 }
 
 static gboolean
diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.c b/src/core/devices/wifi/nm-device-iwd-p2p.c
index 40e38321..73fd4716 100644
--- a/src/core/devices/wifi/nm-device-iwd-p2p.c
+++ b/src/core/devices/wifi/nm-device-iwd-p2p.c
@@ -126,14 +126,17 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMSettingWifiP2P  *s_wifi_p2p;
     GBytes            *wfd_ies;
     NMSettingIPConfig *s_ip;
 
     if (!NM_DEVICE_CLASS(nm_device_iwd_p2p_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_wifi_p2p =
diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index e03227cd..47407a1e 100644
--- a/src/core/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -159,7 +159,7 @@ ap_add_remove(NMDeviceIwd *self,
     }
 
     if (priv->enabled && !priv->iwd_autoconnect)
-        nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+        nm_device_recheck_auto_activate_schedule(NM_DEVICE(self));
 
     if (recheck_available_connections)
         nm_device_recheck_available_connections(NM_DEVICE(self));
@@ -208,7 +208,7 @@ remove_all_aps(NMDeviceIwd *self)
         ap_add_remove(self, FALSE, ap, FALSE);
 
     if (!priv->iwd_autoconnect)
-        nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+        nm_device_recheck_auto_activate_schedule(NM_DEVICE(self));
 
     nm_device_recheck_available_connections(NM_DEVICE(self));
 }
@@ -401,7 +401,7 @@ get_ordered_networks_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 
     if (changed) {
         if (!priv->iwd_autoconnect)
-            nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+            nm_device_recheck_auto_activate_schedule(NM_DEVICE(self));
 
         nm_device_recheck_available_connections(NM_DEVICE(self));
     }
@@ -723,7 +723,10 @@ is_ap_known_network(NMIwdManager *manager, NMWifiAP *ap)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceIwd         *self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate  *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
@@ -739,7 +742,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     gsize                ssid_len;
 
     if (!NM_DEVICE_CLASS(nm_device_iwd_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_wireless = nm_connection_get_setting_wireless(connection);
@@ -1682,7 +1685,7 @@ failed:
 
         if (!priv->nm_autoconnect) {
             priv->nm_autoconnect = true;
-            nm_device_emit_recheck_auto_activate(device);
+            nm_device_recheck_auto_activate_schedule(device);
         }
     }
     g_variant_unref(value);
@@ -2302,9 +2305,10 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
          * to reset the retry count so we set no timeout.
          */
         if (priv->iwd_autoconnect) {
-            NMSettingsConnection *sett_conn = nm_act_request_get_settings_connection(req);
-
-            nm_settings_connection_autoconnect_retries_set(sett_conn, 0);
+            nm_manager_devcon_autoconnect_retries_set(nm_device_get_manager(device),
+                                                      device,
+                                                      nm_act_request_get_settings_connection(req),
+                                                      0);
         }
 
         /* With priv->iwd_autoconnect, if we're assuming a connection because
@@ -2908,7 +2912,7 @@ state_changed(NMDeviceIwd *self, const char *new_state)
     if (!priv->iwd_autoconnect && NM_IN_STRSET(new_state, "disconnected")) {
         priv->nm_autoconnect = TRUE;
         if (!can_connect)
-            nm_device_emit_recheck_auto_activate(device);
+            nm_device_recheck_auto_activate_schedule(device);
     }
 }
 
@@ -3104,12 +3108,12 @@ config_changed(NMConfig           *config,
     NMDeviceIwdPrivate *priv       = NM_DEVICE_IWD_GET_PRIVATE(self);
     gboolean            old_iwd_ac = priv->iwd_autoconnect;
 
-    priv->iwd_autoconnect =
-        nm_config_data_get_device_config_boolean(config_data,
-                                                 NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT,
-                                                 NM_DEVICE(self),
-                                                 TRUE,
-                                                 TRUE);
+    priv->iwd_autoconnect = nm_config_data_get_device_config_boolean_by_device(
+        config_data,
+        NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT,
+        NM_DEVICE(self),
+        TRUE,
+        TRUE);
 
     if (old_iwd_ac != priv->iwd_autoconnect && priv->dbus_station_proxy && !priv->current_ap) {
         gs_unref_variant GVariant *value = NULL;
diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c
index 4705f75c..436c7847 100644
--- a/src/core/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/core/devices/wifi/nm-device-olpc-mesh.c
@@ -270,7 +270,7 @@ companion_state_changed_cb(NMDeviceWifi       *companion,
     NMDeviceState     self_state = nm_device_get_state(NM_DEVICE(self));
 
     if (old_state > NM_DEVICE_STATE_DISCONNECTED && state <= NM_DEVICE_STATE_DISCONNECTED) {
-        nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+        nm_device_recheck_auto_activate_schedule(NM_DEVICE(self));
     }
 
     if (self_state < NM_DEVICE_STATE_PREPARE || self_state > NM_DEVICE_STATE_ACTIVATED
diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c
index 424464c1..fa8cb8fa 100644
--- a/src/core/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.c
@@ -233,10 +233,13 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     if (!NM_DEVICE_CLASS(nm_device_wifi_p2p_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     /* TODO: Allow limitting the interface using the HW-address? */
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 03625f8d..43772834 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -483,7 +483,7 @@ _scan_notify_is_scanning(NMDeviceWifi *self)
 
     if (!_scan_is_scanning_eval(priv)) {
         if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED)
-            nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+            nm_device_recheck_auto_activate_schedule(NM_DEVICE(self));
         nm_device_remove_pending_action(NM_DEVICE(self), NM_PENDING_ACTION_WIFI_SCAN, FALSE);
     }
 
@@ -843,7 +843,7 @@ ap_add_remove(NMDeviceWifi *self,
         nm_dbus_object_clear_and_unexport(&ap);
     }
 
-    nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+    nm_device_recheck_auto_activate_schedule(NM_DEVICE(self));
     if (recheck_available_connections)
         nm_device_recheck_available_connections(NM_DEVICE(self));
 }
@@ -981,7 +981,10 @@ deactivate_reset_hw_addr(NMDevice *device)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     NMDeviceWifi              *self = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate       *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
@@ -995,7 +998,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     const char                *key_mgmt;
 
     if (!NM_DEVICE_CLASS(nm_device_wifi_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     s_wireless = nm_connection_get_setting_wireless(connection);
@@ -1395,7 +1398,7 @@ _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset)
 
     priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
-    randomize = nm_config_data_get_device_config_boolean(
+    randomize = nm_config_data_get_device_config_boolean_by_device(
         NM_CONFIG_GET_DATA,
         NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS,
         device,
@@ -1428,7 +1431,7 @@ _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset)
          * a new one.*/
         priv->hw_addr_scan_expire = now + SCAN_RAND_MAC_ADDRESS_EXPIRE_SEC;
 
-        generate_mac_address_mask = nm_config_data_get_device_config(
+        generate_mac_address_mask = nm_config_data_get_device_config_by_device(
             NM_CONFIG_GET_DATA,
             NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_GENERATE_MAC_ADDRESS_MASK,
             device,
diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c
index b83120f9..a1050c3f 100644
--- a/src/core/devices/wwan/nm-device-modem.c
+++ b/src/core/devices/wwan/nm-device-modem.c
@@ -383,12 +383,15 @@ get_type_description(NMDevice *device)
 }
 
 static gboolean
-check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+check_connection_compatible(NMDevice     *device,
+                            NMConnection *connection,
+                            gboolean      check_properties,
+                            GError      **error)
 {
     GError *local = NULL;
 
     if (!NM_DEVICE_CLASS(nm_device_modem_parent_class)
-             ->check_connection_compatible(device, connection, error))
+             ->check_connection_compatible(device, connection, check_properties, error))
         return FALSE;
 
     if (!nm_modem_check_connection_compatible(NM_DEVICE_MODEM_GET_PRIVATE(device)->modem,
diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c
index f0907c46..a150040f 100644
--- a/src/core/devices/wwan/nm-modem-broadband.c
+++ b/src/core/devices/wwan/nm-modem-broadband.c
@@ -20,6 +20,8 @@
 
 #define NM_MODEM_BROADBAND_MODEM "modem"
 
+#define MM_SUPPORTS_INITIAL_EPS_BEARER_SETTINGS MM_CHECK_VERSION(1, 10, 0)
+
 #if !MM_CHECK_VERSION(1, 14, 0)
 #define MM_MODEM_CAPABILITY_5GNR ((MMModemCapability) (1 << 6))
 #endif
@@ -44,6 +46,7 @@ typedef enum {
     CONNECT_STEP_WAIT_FOR_SIM,
     CONNECT_STEP_UNLOCK,
     CONNECT_STEP_WAIT_FOR_READY,
+    CONNECT_STEP_INTIAL_EPS_BEARER,
     CONNECT_STEP_CONNECT,
     CONNECT_STEP_LAST,
 } ConnectStep;
@@ -560,6 +563,36 @@ out:
     return TRUE;
 }
 
+#if MM_SUPPORTS_INITIAL_EPS_BEARER_SETTINGS
+static void
+set_initial_eps_bearer_settings_ready(MMModem3gpp      *modem_3gpp_iface,
+                                      GAsyncResult     *res,
+                                      NMModemBroadband *self)
+{
+    gs_free_error GError *error = NULL;
+
+    if (!mm_modem_3gpp_set_initial_eps_bearer_settings_finish(modem_3gpp_iface, res, &error)) {
+        if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+            return;
+
+        if (!g_error_matches(error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED)) {
+            _LOGW("failed to set initial EPS bearer settings: %s", error->message);
+            nm_modem_emit_prepare_result(NM_MODEM(self),
+                                         FALSE,
+                                         NM_DEVICE_STATE_REASON_GSM_APN_FAILED);
+            connect_context_clear(self);
+            return;
+        }
+
+        _LOGD("failed to set initial EPS bearer settings due to lack of support: %s",
+              error->message);
+    }
+
+    self->_priv.ctx->step++;
+    connect_context_step(self);
+}
+#endif
+
 static void
 connect_context_step(NMModemBroadband *self)
 {
@@ -629,6 +662,56 @@ connect_context_step(NMModemBroadband *self)
         ctx->step++;
     }
         /* fall-through */
+
+    case CONNECT_STEP_INTIAL_EPS_BEARER:
+        if (MODEM_CAPS_3GPP(ctx->caps)) {
+            NMSettingGsm *s_gsm     = nm_connection_get_setting_gsm(ctx->connection);
+            const char   *apn       = nm_setting_gsm_get_initial_eps_apn(s_gsm);
+            gboolean      do_config = nm_setting_gsm_get_initial_eps_config(s_gsm);
+
+            /* assume do_config is true if an APN is set */
+            if (apn || do_config) {
+#if MM_SUPPORTS_INITIAL_EPS_BEARER_SETTINGS
+                gs_unref_object MMBearerProperties *config = NULL;
+                NMModemIPType ip_type = nm_modem_get_initial_eps_bearer_ip_type(ctx->ip_types);
+
+                config = mm_bearer_properties_new();
+                switch (ip_type) {
+                case NM_MODEM_IP_TYPE_IPV4:
+                    mm_bearer_properties_set_ip_type(config, MM_BEARER_IP_FAMILY_IPV4);
+                    break;
+                case NM_MODEM_IP_TYPE_IPV6:
+                    mm_bearer_properties_set_ip_type(config, MM_BEARER_IP_FAMILY_IPV6);
+                    break;
+                case NM_MODEM_IP_TYPE_IPV4V6:
+                    mm_bearer_properties_set_ip_type(config, MM_BEARER_IP_FAMILY_IPV4V6);
+                    break;
+                default:
+                    /* do nothing */
+                    break;
+                }
+                if (apn)
+                    mm_bearer_properties_set_apn(config, apn);
+
+                /*
+                 * Setting the initial EPS bearer settings is a no-op in
+                 * ModemManager if the desired configuration is already active.
+                 */
+                mm_modem_3gpp_set_initial_eps_bearer_settings(
+                    self->_priv.modem_3gpp_iface,
+                    config,
+                    ctx->cancellable,
+                    (GAsyncReadyCallback) set_initial_eps_bearer_settings_ready,
+                    self);
+                break;
+#else
+                _LOGD("cannot set initial EPS bearer settings due to old ModemManager version");
+#endif
+            }
+        }
+        ctx->step++;
+        /* fall-through */
+
     case CONNECT_STEP_CONNECT:
         if (!ctx->connect_properties)
             break;
diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c
index 0159d351..ea0fa7aa 100644
--- a/src/core/devices/wwan/nm-modem.c
+++ b/src/core/devices/wwan/nm-modem.c
@@ -558,6 +558,37 @@ nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError
     return NULL;
 }
 
+/**
+ * nm_modem_get_initial_eps_bearer_ip_type:
+ * @connection_ip_types: the #NMModemIPType as returned by
+ * nm_modem_get_connection_ip_type
+ *
+ * Given the connection IP types, this function returns which IP type to use when
+ * configuring the initial EPS bearer.
+ *
+ * Returns: the #NMModemIpType value to use for the initial EPS bearer
+ */
+NMModemIPType
+nm_modem_get_initial_eps_bearer_ip_type(const GArray *connection_ip_types)
+{
+    NMModemIPType ip_types = NM_MODEM_IP_TYPE_UNKNOWN;
+    guint         i;
+
+    nm_assert(connection_ip_types);
+
+    for (i = 0; i < connection_ip_types->len; i++)
+        ip_types |= nm_g_array_index(connection_ip_types, NMModemIPType, i);
+
+    nm_assert(ip_types != NM_MODEM_IP_TYPE_UNKNOWN);
+
+    if (ip_types & NM_MODEM_IP_TYPE_IPV4V6)
+        return NM_MODEM_IP_TYPE_IPV4V6;
+    if (ip_types & NM_MODEM_IP_TYPE_IPV4)
+        return NM_MODEM_IP_TYPE_IPV4;
+
+    return NM_MODEM_IP_TYPE_IPV6;
+}
+
 const char *
 nm_modem_get_device_id(NMModem *self)
 {
diff --git a/src/core/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h
index ec001102..021d77b2 100644
--- a/src/core/devices/wwan/nm-modem.h
+++ b/src/core/devices/wwan/nm-modem.h
@@ -226,6 +226,8 @@ void nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason);
 
 GArray *nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error);
 
+NMModemIPType nm_modem_get_initial_eps_bearer_ip_type(const GArray *connection_ip_types);
+
 /* For subclasses */
 
 void nm_modem_emit_signal_new_config(NMModem                  *self,
diff --git a/src/core/dhcp/nm-dhcp-client-logging.h b/src/core/dhcp/nm-dhcp-client-logging.h
index 2b0d8d06..3a2b927a 100644
--- a/src/core/dhcp/nm-dhcp-client-logging.h
+++ b/src/core/dhcp/nm-dhcp-client-logging.h
@@ -41,16 +41,19 @@ _nm_dhcp_client_get_domain(NMDhcpClient *self)
         if (nm_logging_enabled(_level, _NMLOG_DOMAIN)) {                                 \
             NMDhcpClient     *_self    = (NMDhcpClient *) (self);                        \
             const char       *__ifname = _self ? nm_dhcp_client_get_iface(_self) : NULL; \
+            const char       *_type    = nm_dhcp_client_get_iface_type_for_log(_self);   \
             const NMLogDomain _domain  = _nm_dhcp_client_get_domain(_self);              \
                                                                                          \
             nm_log(_level,                                                               \
                    _domain,                                                              \
                    __ifname,                                                             \
                    NULL,                                                                 \
-                   "%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                    \
+                   "%s%s%s%s%s%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),            \
                    _NMLOG_PREFIX_NAME,                                                   \
                    (_domain == LOGD_DHCP4 ? "4" : (_domain == LOGD_DHCP6 ? "6" : "")),   \
-                   NM_PRINT_FMT_QUOTED(__ifname, " (", __ifname, ")", "")                \
+                   (__ifname || _type) ? " " : "",                                       \
+                   NM_PRINT_FMT_QUOTED(__ifname, "(", __ifname, ")", ""),                \
+                   NM_PRINT_FMT_QUOTED(_type, "[", _type, "]", "")                       \
                        _NM_UTILS_MACRO_REST(__VA_ARGS__));                               \
         }                                                                                \
     }                                                                                    \
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index b10ce410..6978bd3c 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -196,6 +196,14 @@ nm_dhcp_client_get_iface(NMDhcpClient *self)
     return priv->config.iface;
 }
 
+const char *
+nm_dhcp_client_get_iface_type_for_log(NMDhcpClient *self)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    return priv->config.iface_type_log;
+}
+
 NMDedupMultiIndex *
 nm_dhcp_client_get_multi_idx(NMDhcpClient *self)
 {
@@ -1823,6 +1831,7 @@ config_init(NMDhcpClientConfig *config, const NMDhcpClientConfig *src)
     nm_g_bytes_ref(config->client_id);
 
     config->iface           = g_strdup(config->iface);
+    config->iface_type_log  = g_strdup(config->iface_type_log);
     config->uuid            = g_strdup(config->uuid);
     config->anycast_address = g_strdup(config->anycast_address);
     config->hostname        = g_strdup(config->hostname);
@@ -1885,6 +1894,7 @@ config_clear(NMDhcpClientConfig *config)
     nm_clear_pointer(&config->client_id, g_bytes_unref);
 
     nm_clear_g_free((gpointer *) &config->iface);
+    nm_clear_g_free((gpointer *) &config->iface_type_log);
     nm_clear_g_free((gpointer *) &config->uuid);
     nm_clear_g_free((gpointer *) &config->anycast_address);
     nm_clear_g_free((gpointer *) &config->hostname);
diff --git a/src/core/dhcp/nm-dhcp-client.h b/src/core/dhcp/nm-dhcp-client.h
index f43770a0..903ea6ac 100644
--- a/src/core/dhcp/nm-dhcp-client.h
+++ b/src/core/dhcp/nm-dhcp-client.h
@@ -103,6 +103,10 @@ typedef struct {
 
     const char *iface;
 
+    /* Interface type for logging; only set for some devices whose names can be
+     * ambiguous. */
+    const char *iface_type_log;
+
     /* The hardware address */
     GBytes *hwaddr;
 
@@ -174,6 +178,10 @@ typedef struct {
             /* Number to prefixes (IA_PD) to request */
             guint needed_prefixes;
 
+            /* A hint to send to server for prefix delegation (IA_PD). */
+            struct in6_addr pd_hint_addr;
+            guint8          pd_hint_length;
+
             /* Use Information-request to get stateless configuration
              * parameters (don't request a IA_NA) */
             bool info_only : 1;
@@ -260,6 +268,7 @@ gboolean nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer
 
 int                nm_dhcp_client_get_addr_family(NMDhcpClient *self);
 const char        *nm_dhcp_client_get_iface(NMDhcpClient *self);
+const char        *nm_dhcp_client_get_iface_type_for_log(NMDhcpClient *self);
 NMDedupMultiIndex *nm_dhcp_client_get_multi_idx(NMDhcpClient *self);
 int                nm_dhcp_client_get_ifindex(NMDhcpClient *self);
 
diff --git a/src/core/dhcp/nm-dhcp-dhclient.c b/src/core/dhcp/nm-dhcp-dhclient.c
index 35b2fb2e..4aab4b1e 100644
--- a/src/core/dhcp/nm-dhcp-dhclient.c
+++ b/src/core/dhcp/nm-dhcp-dhclient.c
@@ -356,6 +356,7 @@ dhclient_start(NMDhcpClient *client,
     gs_free char                *preferred_leasefile_path = NULL;
     int                          addr_family;
     const NMDhcpClientConfig    *client_config;
+    char                         pd_length_str[16];
 
     g_return_val_if_fail(!priv->pid_file, FALSE);
     client_config = nm_dhcp_client_get_config(client);
@@ -463,6 +464,17 @@ dhclient_start(NMDhcpClient *client,
 
         if (mode_opt)
             g_ptr_array_add(argv, (gpointer) mode_opt);
+
+        if (prefixes > 0 && client_config->v6.pd_hint_length > 0) {
+            if (!IN6_IS_ADDR_UNSPECIFIED(&client_config->v6.pd_hint_addr)) {
+                _LOGW("dhclient only supports a length as prefix delegation hint, not a prefix");
+            }
+
+            nm_sprintf_buf(pd_length_str, "%u", client_config->v6.pd_hint_length);
+            g_ptr_array_add(argv, "--prefix-len-hint");
+            g_ptr_array_add(argv, pd_length_str);
+        }
+
         while (prefixes--)
             g_ptr_array_add(argv, (gpointer) "-P");
     }
diff --git a/src/core/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c
index 213d9496..ee95abb7 100644
--- a/src/core/dhcp/nm-dhcp-helper.c
+++ b/src/core/dhcp/nm-dhcp-helper.c
@@ -21,21 +21,22 @@
 #define _NMLOG_ENABLED(level) ((level) <= LOG_ERR)
 #endif
 
-#define _NMLOG(always_enabled, level, ...)                                                       \
-    G_STMT_START                                                                                 \
-    {                                                                                            \
-        if ((always_enabled) || _NMLOG_ENABLED(level)) {                                         \
-            GTimeVal _tv;                                                                        \
-                                                                                                 \
-            g_get_current_time(&_tv);                                                            \
-            g_print(                                                                             \
-                "nm-dhcp-helper[%ld] %-7s [%ld.%04ld] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) "\n", \
-                (long) getpid(),                                                                 \
-                nm_utils_syslog_to_str(level),                                                   \
-                _tv.tv_sec,                                                                      \
-                _tv.tv_usec / 100 _NM_UTILS_MACRO_REST(__VA_ARGS__));                            \
-        }                                                                                        \
-    }                                                                                            \
+#define _NMLOG(always_enabled, level, ...)                                     \
+    G_STMT_START                                                               \
+    {                                                                          \
+        if ((always_enabled) || _NMLOG_ENABLED(level)) {                       \
+            gint64 _tv;                                                        \
+                                                                               \
+            _tv = g_get_real_time();                                           \
+            g_print("nm-dhcp-helper[%ld] %-7s [%" G_GINT64_FORMAT              \
+                    ".%04d] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) "\n",         \
+                    (long) getpid(),                                           \
+                    nm_utils_syslog_to_str(level),                             \
+                    (_tv / NM_UTILS_USEC_PER_SEC),                             \
+                    ((int) ((_tv % NM_UTILS_USEC_PER_SEC) / (((gint64) 100)))) \
+                        _NM_UTILS_MACRO_REST(__VA_ARGS__));                    \
+        }                                                                      \
+    }                                                                          \
     G_STMT_END
 
 #define _LOGD(...) _NMLOG(TRUE, LOG_INFO, __VA_ARGS__)
diff --git a/src/core/dhcp/nm-dhcp-options.c b/src/core/dhcp/nm-dhcp-options.c
index 33a9f4ed..7c47c82e 100644
--- a/src/core/dhcp/nm-dhcp-options.c
+++ b/src/core/dhcp/nm-dhcp-options.c
@@ -199,6 +199,7 @@ const NMDhcpOption _nm_dhcp_option_dhcp6_options[] = {
 
     REQ(NM_DHCP_OPTION_DHCP6_DNS_SERVERS, "dhcp6_name_servers", TRUE),
     REQ(NM_DHCP_OPTION_DHCP6_DOMAIN_LIST, "dhcp6_domain_search", TRUE),
+    REQ(NM_DHCP_OPTION_DHCP6_IA_PD, "ip6_prefix", FALSE),
     REQ(NM_DHCP_OPTION_DHCP6_SNTP_SERVERS, "dhcp6_sntp_servers", TRUE),
     REQ(NM_DHCP_OPTION_DHCP6_FQDN, "fqdn_fqdn", FALSE),
     REQ(NM_DHCP_OPTION_DHCP6_NTP_SERVER, "dhcp6_ntp_servers", TRUE),
@@ -237,6 +238,7 @@ static const NMDhcpOption *const _sorted_options_6[G_N_ELEMENTS(_nm_dhcp_option_
     A(14),
     A(15),
     A(16),
+    A(17),
 #undef A
 };
 
diff --git a/src/core/dhcp/nm-dhcp-options.h b/src/core/dhcp/nm-dhcp-options.h
index 050080d9..1c61c74d 100644
--- a/src/core/dhcp/nm-dhcp-options.h
+++ b/src/core/dhcp/nm-dhcp-options.h
@@ -161,6 +161,7 @@ typedef enum {
     NM_DHCP_OPTION_DHCP6_SERVER_ID    = 2,
     NM_DHCP_OPTION_DHCP6_DNS_SERVERS  = 23,
     NM_DHCP_OPTION_DHCP6_DOMAIN_LIST  = 24,
+    NM_DHCP_OPTION_DHCP6_IA_PD        = 25, /* RFC 8415 */
     NM_DHCP_OPTION_DHCP6_SNTP_SERVERS = 31,
     NM_DHCP_OPTION_DHCP6_FQDN         = 39,
     NM_DHCP_OPTION_DHCP6_NTP_SERVER   = 56, /* RFC 5908 */
@@ -188,7 +189,7 @@ typedef struct {
 } NMDhcpOption;
 
 extern const NMDhcpOption _nm_dhcp_option_dhcp4_options[143];
-extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[17];
+extern const NMDhcpOption _nm_dhcp_option_dhcp6_options[18];
 
 static inline const char *
 nm_dhcp_option_get_name(const NMDhcpOption *option)
diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c
index 6f9312da..0fc5f928 100644
--- a/src/core/dhcp/nm-dhcp-systemd.c
+++ b/src/core/dhcp/nm-dhcp-systemd.c
@@ -155,6 +155,26 @@ lease_to_ip6_config(NMDhcpSystemd *self, sd_dhcp6_lease *lease, gint32 ts, GErro
                                   str->str);
     }
 
+    {
+        struct in6_addr prefix;
+        uint8_t         prefix_len;
+
+        nm_gstring_prepare(&str);
+        sd_dhcp6_lease_reset_pd_prefix_iter(lease);
+        while (!sd_dhcp6_lease_get_pd(lease, &prefix, &prefix_len, NULL, NULL)) {
+            nm_gstring_add_space_delimiter(str);
+            nm_inet6_ntop(&prefix, addr_str);
+            g_string_append_printf(str, "%s/%u", addr_str, prefix_len);
+        }
+        if (str->len > 0) {
+            nm_dhcp_option_add_option(options,
+                                      TRUE,
+                                      AF_INET6,
+                                      NM_DHCP_OPTION_DHCP6_IA_PD,
+                                      str->str);
+        }
+    }
+
     num = sd_dhcp6_lease_get_domains(lease, &domains);
     if (num > 0) {
         nm_gstring_prepare(&str);
@@ -366,6 +386,15 @@ ip6_start(NMDhcpClient *client, const struct in6_addr *ll_addr, GError **error)
             _LOGW("dhcp-client6: only one prefix request is supported");
         }
         prefix_delegation = TRUE;
+        if (client_config->v6.pd_hint_length > 0) {
+            r = sd_dhcp6_client_set_prefix_delegation_hint(sd_client,
+                                                           client_config->v6.pd_hint_length,
+                                                           &client_config->v6.pd_hint_addr);
+            if (r < 0) {
+                nm_utils_error_set_errno(error, r, "failed to set prefix delegation hint: %s");
+                return FALSE;
+            }
+        }
     }
     r = sd_dhcp6_client_set_prefix_delegation(sd_client, prefix_delegation);
     if (r < 0) {
diff --git a/src/core/dns/nm-dns-dnsmasq.c b/src/core/dns/nm-dns-dnsmasq.c
index 05aeff49..53e40f59 100644
--- a/src/core/dns/nm-dns-dnsmasq.c
+++ b/src/core/dns/nm-dns-dnsmasq.c
@@ -527,7 +527,6 @@ _gl_pid_spawn_next_step(void)
     argv[argv_idx++] = "--cache-size=400";
     argv[argv_idx++] = "--clear-on-reload";     /* clear cache when dns server changes */
     argv[argv_idx++] = "--conf-file=/dev/null"; /* avoid loading /etc/dnsmasq.conf */
-    argv[argv_idx++] = "--proxy-dnssec";        /* Allow DNSSEC to pass through */
     argv[argv_idx++] = "--enable-dbus=" DNSMASQ_DBUS_SERVICE;
 
     /* dnsmasq exits if the conf dir is not present */
diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c
index 6ee2e816..53564693 100644
--- a/src/core/dns/nm-dns-manager.c
+++ b/src/core/dns/nm-dns-manager.c
@@ -125,6 +125,9 @@ typedef struct {
 
     NMConfig *config;
 
+    NMDnsConfigIPData *best_ip_config_4;
+    NMDnsConfigIPData *best_ip_config_6;
+
     struct {
         guint64 ts;
         guint   num_restarts;
@@ -173,14 +176,46 @@ NM_DEFINE_SINGLETON_GETTER(NMDnsManager, nm_dns_manager_get, NM_TYPE_DNS_MANAGER
 /*****************************************************************************/
 
 static gboolean
-domain_is_valid(const char *domain, gboolean check_public_suffix)
+domain_is_valid(const char *domain,
+                gboolean    reject_public_suffix,
+                gboolean    assume_any_tld_is_public)
 {
     if (*domain == '\0')
         return FALSE;
-#if WITH_LIBPSL
-    if (check_public_suffix && psl_is_public_suffix(psl_builtin(), domain))
-        return FALSE;
+
+    if (reject_public_suffix) {
+        int is_pub;
+
+#if !WITH_LIBPSL
+        /* Without libpsl, we cannot detect that the domain is a public suffix, we assume
+         * the domain is not and valid. */
+        is_pub = FALSE;
+#elif defined(PSL_TYPE_NO_STAR_RULE)
+        /*
+         * If we use PSL_TYPE_ANY, any TLD (top-level domain, i.e., domain
+         * with no dots) is considered *public* by the PSL library even if
+         * it is *not* on the official suffix list. This is the implicit
+         * behavior of the older API function psl_is_public_suffix().
+         * To inhibit that and only deem TLDs explicitly listed in the PSL
+         * as public, we need to turn off the "prevailing star rule" with
+         * PSL_TYPE_NO_STAR_RULE.
+         * For documentation on psl_is_public_suffix2(), see:
+         * https://rockdaboot.github.io/libpsl/libpsl-Public-Suffix-List-functions.html#psl-is-public-suffix2
+         * For more on the public suffix format, including wildcards:
+         * https://github.com/publicsuffix/list/wiki/Format#format
+         */
+        is_pub =
+            psl_is_public_suffix2(psl_builtin(),
+                                  domain,
+                                  assume_any_tld_is_public ? PSL_TYPE_ANY : PSL_TYPE_NO_STAR_RULE);
+#else
+        is_pub = psl_is_public_suffix(psl_builtin(), domain);
 #endif
+
+        if (is_pub)
+            return FALSE;
+    }
+
     return TRUE;
 }
 
@@ -533,7 +568,7 @@ add_dns_domains(GPtrArray            *array,
         str = searches[i];
         if (!include_routing && domain_is_routing(str))
             continue;
-        if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE))
+        if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE, TRUE))
             continue;
         add_string_item(array, str, dup);
     }
@@ -542,7 +577,7 @@ add_dns_domains(GPtrArray            *array,
             str = domains[i];
             if (!include_routing && domain_is_routing(str))
                 continue;
-            if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE))
+            if (!domain_is_valid(nm_utils_parse_dns_domain(str, NULL), FALSE, TRUE))
                 continue;
             add_string_item(array, str, dup);
         }
@@ -647,7 +682,7 @@ run_netconfig(NMDnsManager *self, GError **error, int *stdin_fd)
     if (!g_spawn_async_with_pipes(NULL,
                                   argv,
                                   NULL,
-                                  G_SPAWN_DO_NOT_REAP_CHILD,
+                                  G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD,
                                   NULL,
                                   NULL,
                                   &pid,
@@ -1236,7 +1271,7 @@ merge_global_dns_config(NMResolvConfData *rc, NMGlobalDnsConfig *global_conf)
         for (i = 0; searches[i]; i++) {
             if (domain_is_routing(searches[i]))
                 continue;
-            if (!domain_is_valid(searches[i], FALSE))
+            if (!domain_is_valid(searches[i], FALSE, TRUE))
                 continue;
             add_string_item(rc->searches, searches[i], TRUE);
         }
@@ -1946,6 +1981,7 @@ nm_dns_manager_set_ip_config(NMDnsManager         *self,
     NMDnsConfigIPData   *ip_data = NULL;
     int                  dns_priority;
     gboolean             any_removed = FALSE;
+    NMDnsConfigIPData  **p_best;
 
     g_return_val_if_fail(NM_IS_DNS_MANAGER(self), FALSE);
     g_return_val_if_fail(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd), FALSE);
@@ -2013,6 +2049,12 @@ nm_dns_manager_set_ip_config(NMDnsManager         *self,
             }
 
             any_removed = TRUE;
+
+            if (priv->best_ip_config_4 == ip_data_iter)
+                priv->best_ip_config_4 = NULL;
+            if (priv->best_ip_config_6 == ip_data_iter)
+                priv->best_ip_config_6 = NULL;
+
             _dns_config_ip_data_free(ip_data_iter);
         }
     }
@@ -2063,6 +2105,19 @@ nm_dns_manager_set_ip_config(NMDnsManager         *self,
         changed                 = TRUE;
     }
 
+    p_best = NM_IS_IPv4(addr_family) ? &priv->best_ip_config_4 : &priv->best_ip_config_6;
+    if (ip_config_type == NM_DNS_IP_CONFIG_TYPE_BEST_DEVICE) {
+        /* Only one best-device per IP version is allowed */
+        if (*p_best != ip_data) {
+            if (*p_best)
+                (*p_best)->ip_config_type = NM_DNS_IP_CONFIG_TYPE_DEFAULT;
+            *p_best = ip_data;
+        }
+    } else {
+        if (*p_best == ip_data)
+            *p_best = NULL;
+    }
+
     if (changed)
         priv->ip_data_lst_need_sort = TRUE;
 
@@ -2100,7 +2155,8 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s
 
     /* Certain hostnames we don't want to include in resolv.conf 'searches' */
     if (hostname && nm_utils_is_specific_hostname(hostname)
-        && !g_str_has_suffix(hostname, ".in-addr.arpa") && !nm_inet_is_valid(AF_UNSPEC, hostname)) {
+        && !NM_STR_HAS_SUFFIX(hostname, ".in-addr.arpa")
+        && !nm_inet_is_valid(AF_UNSPEC, hostname)) {
         domain = strchr(hostname, '.');
         if (domain) {
             domain++;
@@ -2111,11 +2167,16 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s
              * specified, this makes a good default.) However, if the
              * hostname is the top level of a domain (eg, "example.com"),
              * then use the hostname itself as the search (since the user
-             * is unlikely to want "com" as a search domain).a
+             * is unlikely to want "com" as a search domain).
+             *
+             * Because that logic only applies to public domains, the
+             * "assume_any_tld_is_public" parameter is FALSE. For
+             * example, it is likely that the user *does* want "local"
+             * or "localdomain" as a search domain.
              */
-            if (domain_is_valid(domain, TRUE)) {
+            if (domain_is_valid(domain, TRUE, FALSE)) {
                 /* pass */
-            } else if (domain_is_valid(hostname, TRUE)) {
+            } else if (domain_is_valid(hostname, TRUE, FALSE)) {
                 domain = hostname;
             }
 
@@ -2127,6 +2188,8 @@ nm_dns_manager_set_hostname(NMDnsManager *self, const char *hostname, gboolean s
     if (!nm_strdup_reset(&priv->hostdomain, domain))
         return;
 
+    _LOGT("set host domain to %s%s%s", NM_PRINT_FMT_QUOTE_STRING(priv->hostdomain));
+
     if (skip_update)
         return;
 
@@ -2779,6 +2842,9 @@ dispose(GObject *object)
 
     nm_clear_g_source_inst(&priv->update_pending_unblock);
 
+    priv->best_ip_config_4 = NULL;
+    priv->best_ip_config_6 = NULL;
+
     c_list_for_each_entry_safe (ip_data, ip_data_safe, &priv->ip_data_lst_head, ip_data_lst)
         _dns_config_ip_data_free(ip_data);
 
diff --git a/src/core/main.c b/src/core/main.c
index 2eb230d9..4c7de6cd 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -266,13 +266,14 @@ _dbus_manager_init(NMConfig *config)
 
     c_a_q_type = nm_config_get_configure_and_quit(config);
 
-    if (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED)
-        return nm_dbus_manager_acquire_bus(busmgr, TRUE);
+    if (c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD) {
+        /* in initrd we don't have D-Bus at all. Don't even try to get the G_BUS_TYPE_SYSTEM
+         * connection. And of course don't claim the D-Bus name. */
+        return TRUE;
+    }
 
-    nm_assert(c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_INITRD);
-    /* in initrd we don't have D-Bus at all. Don't even try to get the G_BUS_TYPE_SYSTEM
-     * connection. And of course don't claim the D-Bus name. */
-    return TRUE;
+    nm_assert(c_a_q_type == NM_CONFIG_CONFIGURE_AND_QUIT_DISABLED);
+    return nm_dbus_manager_setup(busmgr);
 }
 
 /*
@@ -507,6 +508,9 @@ main(int argc, char *argv[])
     nm_log_dbg(LOGD_CORE, "setting up local loopback");
     nm_platform_link_change_flags(NM_PLATFORM_GET, 1, IFF_UP, TRUE);
 
+    if (!nm_dbus_manager_request_name_sync(nm_dbus_manager_get()))
+        goto done;
+
     success = TRUE;
 
     if (configure_and_quit == FALSE) {
diff --git a/src/core/nm-act-request.c b/src/core/nm-act-request.c
index 55d1829e..dce18ba4 100644
--- a/src/core/nm-act-request.c
+++ b/src/core/nm-act-request.c
@@ -416,8 +416,8 @@ nm_act_request_init(NMActRequest *req)
 /**
  * nm_act_request_new:
  *
- * @settings_connection: (allow-none): the connection to activate @device with
- * @applied_connection: (allow-none): the applied connection
+ * @settings_connection: (nullable): the connection to activate @device with
+ * @applied_connection: (nullable): the applied connection
  * @specific_object: the object path of the specific object (ie, Wi-Fi access point,
  *    etc) that will be used to activate @connection and @device
  * @subject: the #NMAuthSubject representing the requestor of the activation
diff --git a/src/core/nm-active-connection.c b/src/core/nm-active-connection.c
index 6f62a601..36a11f71 100644
--- a/src/core/nm-active-connection.c
+++ b/src/core/nm-active-connection.c
@@ -1153,7 +1153,7 @@ auth_done(NMAuthManager       *auth_mgr,
 /**
  * nm_active_connection_authorize:
  * @self: the #NMActiveConnection
- * @initial_connection: (allow-none): for add-and-activate, there
+ * @initial_connection: (nullable): for add-and-activate, there
  *   is no @settings_connection available when creating the active connection.
  *   Instead pass an alternative connection.
  * @result_func: function to be called on success or error
diff --git a/src/core/nm-auth-utils.c b/src/core/nm-auth-utils.c
index cef926fd..7739f443 100644
--- a/src/core/nm-auth-utils.c
+++ b/src/core/nm-auth-utils.c
@@ -251,7 +251,7 @@ nm_auth_chain_steal_data(NMAuthChain *self, const char *tag)
  * @tag: the tag for referencing the attached data.
  * @data: the data to attach. If %NULL, this call has no effect
  *   and nothing is attached.
- * @data_destroy: (allow-none): the destroy function for the data pointer.
+ * @data_destroy: (nullable): the destroy function for the data pointer.
  *
  * @tag string is not cloned and must outlive @self. That is why
  * the function is "unsafe". Use nm_auth_chain_set_data() with a C literal
diff --git a/src/core/nm-bond-manager.c b/src/core/nm-bond-manager.c
index 2d15b0b5..9985fccf 100644
--- a/src/core/nm-bond-manager.c
+++ b/src/core/nm-bond-manager.c
@@ -438,6 +438,7 @@ _nft_call(NMBondManager     *self,
 {
     gs_unref_bytes GBytes     *stdin_buf             = NULL;
     gs_free const char *const *previous_members_strv = NULL;
+    gboolean                   with_counters;
 
     if (up) {
         gs_unref_ptrarray GPtrArray *arr = NULL;
@@ -480,11 +481,16 @@ _nft_call(NMBondManager     *self,
         }
     }
 
+    /* counters in the nft rules are convenient for debugging, but have a performance overhead.
+     * Enable counters based on whether NM logging is enabled. */
+    with_counters = _NMLOG_ENABLED(LOGL_TRACE);
+
     stdin_buf = nm_firewall_nft_stdio_mlag(up,
                                            bond_ifname,
                                            bond_ifnames_down,
                                            active_members,
-                                           previous_members_strv);
+                                           previous_members_strv,
+                                           with_counters);
 
     nm_clear_g_cancellable(&self->cancellable);
     self->cancellable = g_cancellable_new();
diff --git a/src/core/nm-checkpoint.c b/src/core/nm-checkpoint.c
index cd0e17fa..5c4d4e53 100644
--- a/src/core/nm-checkpoint.c
+++ b/src/core/nm-checkpoint.c
@@ -259,17 +259,19 @@ restore_and_activate_connection(NMCheckpoint *self, DeviceCheckpoint *dev_checkp
             g_clear_error(&local_error);
             return FALSE;
         }
-
-        /* If the device is software, a brand new NMDevice may have been created */
-        if (dev_checkpoint->is_software && !dev_checkpoint->device) {
-            dev_checkpoint->device = nm_manager_get_device(priv->manager,
-                                                           dev_checkpoint->original_dev_name,
-                                                           dev_checkpoint->dev_type);
-            nm_g_object_ref(dev_checkpoint->device);
-        }
         need_activation = TRUE;
     }
 
+    /* If the device is software, a brand new NMDevice may have been created
+     * after adding the new connection; or the old device might have been
+     * deleted and we need to fetch it again. */
+    if (dev_checkpoint->is_software && !dev_checkpoint->device) {
+        dev_checkpoint->device = nm_manager_get_device(priv->manager,
+                                                       dev_checkpoint->original_dev_name,
+                                                       dev_checkpoint->dev_type);
+        nm_g_object_ref(dev_checkpoint->device);
+    }
+
     if (!dev_checkpoint->device) {
         _LOGD("rollback: device cannot be restored");
         return FALSE;
@@ -368,7 +370,7 @@ nm_checkpoint_rollback(NMCheckpoint *self)
                 _LOGD("rollback: device was not realized, unmanage it");
                 nm_device_set_unmanaged_by_flags_queue(device,
                                                        NM_UNMANAGED_USER_EXPLICIT,
-                                                       TRUE,
+                                                       NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                                        NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
                 goto next_dev;
             }
@@ -402,7 +404,7 @@ nm_checkpoint_rollback(NMCheckpoint *self)
                 _LOGD("rollback: explicitly unmanage device");
                 nm_device_set_unmanaged_by_flags_queue(device,
                                                        NM_UNMANAGED_USER_EXPLICIT,
-                                                       TRUE,
+                                                       NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                                        NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
             }
             goto next_dev;
@@ -458,8 +460,25 @@ next_dev:
         NMDeviceState state;
 
         nm_manager_for_each_device (priv->manager, device, tmp_lst) {
+            gboolean found = FALSE;
+
             if (g_hash_table_contains(priv->devices, device))
                 continue;
+
+            /* Also ignore devices that were in the checkpoint initially and
+             * were moved to 'removed_devices' because they got removed from
+             * the system. */
+            for (i = 0; i < priv->removed_devices->len; i++) {
+                dev_checkpoint = priv->removed_devices->pdata[i];
+                if (dev_checkpoint->dev_type == nm_device_get_device_type(device)
+                    && nm_streq0(dev_checkpoint->original_dev_name, nm_device_get_iface(device))) {
+                    found = TRUE;
+                    break;
+                }
+            }
+            if (found)
+                continue;
+
             state = nm_device_get_state(device);
             if (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_DEACTIVATING) {
                 _LOGD("rollback: disconnecting new device %s", nm_device_get_iface(device));
diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c
index ff44bc46..ed6d8381 100644
--- a/src/core/nm-config-data.c
+++ b/src/core/nm-config-data.c
@@ -130,6 +130,12 @@ G_DEFINE_TYPE(NMConfigData, nm_config_data, G_TYPE_OBJECT)
 static const char *
 _match_section_info_get_str(const MatchSectionInfo *m, GKeyFile *keyfile, const char *property);
 
+static const char *_config_data_get_device_config(const NMConfigData          *self,
+                                                  const char                  *property,
+                                                  const NMMatchSpecDeviceData *match_data,
+                                                  NMDevice                    *device,
+                                                  gboolean                    *has_match);
+
 /*****************************************************************************/
 
 const char *
@@ -366,7 +372,55 @@ nm_config_data_get_iwd_config_path(const NMConfigData *self)
 }
 
 gboolean
-nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device)
+nm_config_data_get_ignore_carrier_for_port(const NMConfigData *self,
+                                           const char         *master,
+                                           const char         *slave_type)
+{
+    const char           *value;
+    gboolean              has_match;
+    int                   m;
+    NMMatchSpecDeviceData match_data;
+
+    g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE);
+
+    if (!master || !slave_type)
+        goto out_default;
+
+    if (!nm_utils_ifname_valid_kernel(master, NULL))
+        goto out_default;
+
+    match_data = (NMMatchSpecDeviceData){
+        .interface_name = master,
+        .device_type    = slave_type,
+    };
+
+    value = _config_data_get_device_config(self,
+                                           NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER,
+                                           &match_data,
+                                           NULL,
+                                           &has_match);
+    if (has_match)
+        m = nm_config_parse_boolean(value, -1);
+    else {
+        NMMatchSpecMatchType x;
+
+        x = nm_match_spec_device(NM_CONFIG_DATA_GET_PRIVATE(self)->ignore_carrier, &match_data);
+        m = nm_match_spec_match_type_to_bool(x, -1);
+    }
+
+    if (NM_IN_SET(m, TRUE, FALSE))
+        return m;
+
+out_default:
+    /* if ignore-carrier is not explicitly or detected for the master, then we assume it's
+     * enabled. This is in line with nm_config_data_get_ignore_carrier_by_device(), where
+     * ignore-carrier is enabled based on nm_device_ignore_carrier_by_default().
+     */
+    return TRUE;
+}
+
+gboolean
+nm_config_data_get_ignore_carrier_by_device(const NMConfigData *self, NMDevice *device)
 {
     const char *value;
     gboolean    has_match;
@@ -375,10 +429,10 @@ nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device)
     g_return_val_if_fail(NM_IS_CONFIG_DATA(self), FALSE);
     g_return_val_if_fail(NM_IS_DEVICE(device), FALSE);
 
-    value = nm_config_data_get_device_config(self,
-                                             NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER,
-                                             device,
-                                             &has_match);
+    value = nm_config_data_get_device_config_by_device(self,
+                                                       NM_CONFIG_KEYFILE_KEY_DEVICE_IGNORE_CARRIER,
+                                                       device,
+                                                       &has_match);
     if (has_match)
         m = nm_config_parse_boolean(value, -1);
     else
@@ -706,6 +760,7 @@ static const struct {
 } default_values[] = {
     {NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NM_CONFIG_DEFAULT_MAIN_PLUGINS},
     {NM_CONFIG_KEYFILE_GROUP_MAIN, "rc-manager", NM_CONFIG_DEFAULT_MAIN_RC_MANAGER},
+    {NM_CONFIG_KEYFILE_GROUP_MAIN, "migrate-ifcfg-rh", NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH},
     {NM_CONFIG_KEYFILE_GROUP_MAIN,
      NM_CONFIG_KEYFILE_KEY_MAIN_AUTH_POLKIT,
      NM_CONFIG_DEFAULT_MAIN_AUTH_POLKIT},
@@ -1488,21 +1543,23 @@ global_dns_equal(NMGlobalDnsConfig *old, NMGlobalDnsConfig *new)
 /*****************************************************************************/
 
 static const MatchSectionInfo *
-_match_section_infos_lookup(const MatchSectionInfo *match_section_infos,
-                            GKeyFile               *keyfile,
-                            const char             *property,
-                            NMDevice               *device,
-                            const NMPlatformLink   *pllink,
-                            const char             *match_device_type,
-                            const char            **out_value)
+_match_section_infos_lookup(const MatchSectionInfo      *match_section_infos,
+                            GKeyFile                    *keyfile,
+                            const char                  *property,
+                            const NMMatchSpecDeviceData *match_data,
+                            NMDevice                    *device,
+                            const char                 **out_value)
 {
-    const char *match_dhcp_plugin;
+    NMMatchSpecDeviceData match_data_local;
+
+    /* Caller must either provide a "match_data" or a "device" (actually,
+     * neither is also fine, albeit unusual). */
+    nm_assert(!match_data || !device);
+    nm_assert(!device || NM_IS_DEVICE(device));
 
     if (!match_section_infos)
         goto out;
 
-    match_dhcp_plugin = nm_dhcp_manager_get_config(nm_dhcp_manager_get());
-
     for (; match_section_infos->group_name; match_section_infos++) {
         const char *value;
         gboolean    match;
@@ -1519,16 +1576,17 @@ _match_section_infos_lookup(const MatchSectionInfo *match_section_infos,
             continue;
 
         if (match_section_infos->match_device.has) {
-            if (device)
-                match = nm_device_spec_match_list(device, match_section_infos->match_device.spec);
-            else if (pllink)
-                match = nm_match_spec_device_by_pllink(pllink,
-                                                       match_device_type,
-                                                       match_dhcp_plugin,
-                                                       match_section_infos->match_device.spec,
-                                                       FALSE);
-            else
-                match = FALSE;
+            NMMatchSpecMatchType m;
+
+            if (G_UNLIKELY(!match_data)) {
+                /* In most cases, we don't actually have any matches. So we "optimize"
+                 * here by allowing the user to specify a NMDEvice directly, and only
+                 * initialize the match-data when needed. */
+                match_data = nm_match_spec_device_data_init_from_device(&match_data_local, device);
+            }
+
+            m     = nm_match_spec_device(match_section_infos->match_device.spec, match_data);
+            match = nm_match_spec_match_type_to_bool(m, FALSE);
         } else
             match = TRUE;
 
@@ -1543,11 +1601,12 @@ out:
     return NULL;
 }
 
-const char *
-nm_config_data_get_device_config(const NMConfigData *self,
-                                 const char         *property,
-                                 NMDevice           *device,
-                                 gboolean           *has_match)
+static const char *
+_config_data_get_device_config(const NMConfigData          *self,
+                               const char                  *property,
+                               const NMMatchSpecDeviceData *match_data,
+                               NMDevice                    *device,
+                               gboolean                    *has_match)
 {
     const NMConfigDataPrivate *priv;
     const MatchSectionInfo    *connection_info;
@@ -1558,20 +1617,40 @@ nm_config_data_get_device_config(const NMConfigData *self,
     g_return_val_if_fail(self, NULL);
     g_return_val_if_fail(property && *property, NULL);
 
+    nm_assert(!match_data || !device);
+    nm_assert(!device || NM_IS_DEVICE(device));
+
     priv = NM_CONFIG_DATA_GET_PRIVATE(self);
 
     connection_info = _match_section_infos_lookup(&priv->device_infos[0],
                                                   priv->keyfile,
                                                   property,
+                                                  match_data,
                                                   device,
-                                                  NULL,
-                                                  NULL,
                                                   &value);
     NM_SET_OUT(has_match, !!connection_info);
     return value;
 }
 
 const char *
+nm_config_data_get_device_config(const NMConfigData          *self,
+                                 const char                  *property,
+                                 const NMMatchSpecDeviceData *match_data,
+                                 gboolean                    *has_match)
+{
+    return _config_data_get_device_config(self, property, match_data, NULL, has_match);
+}
+
+const char *
+nm_config_data_get_device_config_by_device(const NMConfigData *self,
+                                           const char         *property,
+                                           NMDevice           *device,
+                                           gboolean           *has_match)
+{
+    return _config_data_get_device_config(self, property, NULL, device, has_match);
+}
+
+const char *
 nm_config_data_get_device_config_by_pllink(const NMConfigData   *self,
                                            const char           *property,
                                            const NMPlatformLink *pllink,
@@ -1581,53 +1660,58 @@ nm_config_data_get_device_config_by_pllink(const NMConfigData   *self,
     const NMConfigDataPrivate *priv;
     const MatchSectionInfo    *connection_info;
     const char                *value;
+    NMMatchSpecDeviceData      match_data;
 
     g_return_val_if_fail(self, NULL);
     g_return_val_if_fail(property && *property, NULL);
 
     priv = NM_CONFIG_DATA_GET_PRIVATE(self);
 
+    nm_match_spec_device_data_init_from_platform(&match_data,
+                                                 pllink,
+                                                 match_device_type,
+                                                 nm_dhcp_manager_get_config(nm_dhcp_manager_get()));
+
     connection_info = _match_section_infos_lookup(&priv->device_infos[0],
                                                   priv->keyfile,
                                                   property,
+                                                  &match_data,
                                                   NULL,
-                                                  pllink,
-                                                  match_device_type,
                                                   &value);
     NM_SET_OUT(has_match, !!connection_info);
     return value;
 }
 
 gboolean
-nm_config_data_get_device_config_boolean(const NMConfigData *self,
-                                         const char         *property,
-                                         NMDevice           *device,
-                                         int                 val_no_match,
-                                         int                 val_invalid)
+nm_config_data_get_device_config_boolean_by_device(const NMConfigData *self,
+                                                   const char         *property,
+                                                   NMDevice           *device,
+                                                   int                 val_no_match,
+                                                   int                 val_invalid)
 {
     const char *value;
     gboolean    has_match;
 
-    value = nm_config_data_get_device_config(self, property, device, &has_match);
+    value = nm_config_data_get_device_config_by_device(self, property, device, &has_match);
     if (!has_match)
         return val_no_match;
     return nm_config_parse_boolean(value, val_invalid);
 }
 
 gint64
-nm_config_data_get_device_config_int64(const NMConfigData *self,
-                                       const char         *property,
-                                       NMDevice           *device,
-                                       int                 base,
-                                       gint64              min,
-                                       gint64              max,
-                                       gint64              val_no_match,
-                                       gint64              val_invalid)
+nm_config_data_get_device_config_int64_by_device(const NMConfigData *self,
+                                                 const char         *property,
+                                                 NMDevice           *device,
+                                                 int                 base,
+                                                 gint64              min,
+                                                 gint64              max,
+                                                 gint64              val_no_match,
+                                                 gint64              val_invalid)
 {
     const char *value;
     gboolean    has_match;
 
-    value = nm_config_data_get_device_config(self, property, device, &has_match);
+    value = nm_config_data_get_device_config_by_device(self, property, device, &has_match);
     if (!has_match) {
         errno = ENOENT;
         return val_no_match;
@@ -1651,9 +1735,8 @@ nm_config_data_get_device_allowed_connections_specs(const NMConfigData *self,
     connection_info = _match_section_infos_lookup(&priv->device_infos[0],
                                                   priv->keyfile,
                                                   NM_CONFIG_KEYFILE_KEY_DEVICE_ALLOWED_CONNECTIONS,
-                                                  device,
-                                                  NULL,
                                                   NULL,
+                                                  device,
                                                   NULL);
 
     if (connection_info) {
@@ -1696,9 +1779,8 @@ nm_config_data_get_connection_default(const NMConfigData *self,
     _match_section_infos_lookup(&priv->connection_infos[0],
                                 priv->keyfile,
                                 property,
-                                device,
-                                NULL,
                                 NULL,
+                                device,
                                 &value);
     return value;
 }
diff --git a/src/core/nm-config-data.h b/src/core/nm-config-data.h
index e3dc90dd..9e7a50fc 100644
--- a/src/core/nm-config-data.h
+++ b/src/core/nm-config-data.h
@@ -185,7 +185,11 @@ const char *nm_config_data_get_dns_mode(const NMConfigData *self);
 const char *nm_config_data_get_rc_manager(const NMConfigData *self);
 gboolean    nm_config_data_get_systemd_resolved(const NMConfigData *self);
 
-gboolean nm_config_data_get_ignore_carrier(const NMConfigData *self, NMDevice *device);
+gboolean nm_config_data_get_ignore_carrier_for_port(const NMConfigData *self,
+                                                    const char         *master,
+                                                    const char         *slave_type);
+
+gboolean nm_config_data_get_ignore_carrier_by_device(const NMConfigData *self, NMDevice *device);
 gboolean nm_config_data_get_assume_ipv6ll_only(const NMConfigData *self, NMDevice *device);
 int      nm_config_data_get_sriov_num_vfs(const NMConfigData *self, NMDevice *device);
 
@@ -219,10 +223,17 @@ gint64 nm_config_data_get_connection_default_int64(const NMConfigData *self,
                                                    gint64              max,
                                                    gint64              fallback);
 
-const char *nm_config_data_get_device_config(const NMConfigData *self,
-                                             const char         *property,
-                                             NMDevice           *device,
-                                             gboolean           *has_match);
+struct _NMMatchSpecDeviceData;
+
+const char *nm_config_data_get_device_config(const NMConfigData                  *self,
+                                             const char                          *property,
+                                             const struct _NMMatchSpecDeviceData *match_data,
+                                             gboolean                            *has_match);
+
+const char *nm_config_data_get_device_config_by_device(const NMConfigData *self,
+                                                       const char         *property,
+                                                       NMDevice           *device,
+                                                       gboolean           *has_match);
 
 const char *nm_config_data_get_device_config_by_pllink(const NMConfigData   *self,
                                                        const char           *property,
@@ -230,19 +241,19 @@ const char *nm_config_data_get_device_config_by_pllink(const NMConfigData   *sel
                                                        const char           *match_device_type,
                                                        gboolean             *has_match);
 
-gboolean nm_config_data_get_device_config_boolean(const NMConfigData *self,
-                                                  const char         *property,
-                                                  NMDevice           *device,
-                                                  int                 val_no_match,
-                                                  int                 val_invalid);
-gint64   nm_config_data_get_device_config_int64(const NMConfigData *self,
-                                                const char         *property,
-                                                NMDevice           *device,
-                                                int                 base,
-                                                gint64              min,
-                                                gint64              max,
-                                                gint64              val_no_match,
-                                                gint64              val_invalid);
+gboolean nm_config_data_get_device_config_boolean_by_device(const NMConfigData *self,
+                                                            const char         *property,
+                                                            NMDevice           *device,
+                                                            int                 val_no_match,
+                                                            int                 val_invalid);
+gint64   nm_config_data_get_device_config_int64_by_device(const NMConfigData *self,
+                                                          const char         *property,
+                                                          NMDevice           *device,
+                                                          int                 base,
+                                                          gint64              min,
+                                                          gint64              max,
+                                                          gint64              val_no_match,
+                                                          gint64              val_invalid);
 
 const GSList *nm_config_data_get_device_allowed_connections_specs(const NMConfigData *self,
                                                                   NMDevice           *device,
diff --git a/src/core/nm-config.c b/src/core/nm-config.c
index b7445362..b8df41b7 100644
--- a/src/core/nm-config.c
+++ b/src/core/nm-config.c
@@ -853,6 +853,7 @@ static const ConfigGroup config_groups[] = {
                              NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE,
                              NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER,
                              NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH,
+                             NM_CONFIG_KEYFILE_KEY_MAIN_MIGRATE_IFCFG_RH,
                              NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES,
                              NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT,
                              NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS,
@@ -878,6 +879,7 @@ static const ConfigGroup config_groups[] = {
         .group = NM_CONFIG_KEYFILE_GROUP_KEYFILE,
         .keys  = NM_MAKE_STRV(NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME,
                              NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH,
+                             NM_CONFIG_KEYFILE_KEY_KEYFILE_RENAME,
                              NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES, ),
     },
     {
@@ -1531,7 +1533,7 @@ nm_config_keyfile_has_global_dns_config(GKeyFile *keyfile, gboolean internal)
  * intern_config_read:
  * @filename: the filename where to store the internal config
  * @keyfile_conf: the merged configuration from user (/etc/NM/NetworkManager.conf).
- * @out_needs_rewrite: (allow-none): whether the read keyfile contains inconsistent
+ * @out_needs_rewrite: (out) (optional): whether the read keyfile contains inconsistent
  *   data (compared to @keyfile_conf). If %TRUE, you might want to rewrite
  *   the file.
  *
@@ -2058,7 +2060,7 @@ nm_config_set_connectivity_check_enabled(NMConfig *self, gboolean enabled)
 /**
  * nm_config_set_values:
  * @self: the NMConfig instance
- * @keyfile_intern_new: (allow-none): the new internal settings to set.
+ * @keyfile_intern_new: (nullable): the new internal settings to set.
  *   If %NULL, it is equal to an empty keyfile.
  * @allow_write: only if %TRUE, allow writing the changes to file. Otherwise,
  *   do the changes in-memory only.
diff --git a/src/core/nm-config.h b/src/core/nm-config.h
index d56770d0..acec8d05 100644
--- a/src/core/nm-config.h
+++ b/src/core/nm-config.h
@@ -150,6 +150,8 @@ extern char *_nm_config_match_env;
 #define NM_CONFIG_DEVICE_STATE_DIR "" NMRUNDIR "/devices"
 
 #define NM_CONFIG_DEFAULT_LOGGING_AUDIT_BOOL (nm_streq("" NM_CONFIG_DEFAULT_LOGGING_AUDIT, "true"))
+#define NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH_BOOL \
+    (nm_streq("" NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH, "true"))
 
 typedef enum {
     NM_CONFIG_DEVICE_STATE_MANAGED_TYPE_UNKNOWN   = -1,
diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c
index aaf9d810..92de44f1 100644
--- a/src/core/nm-connectivity.c
+++ b/src/core/nm-connectivity.c
@@ -981,9 +981,7 @@ check_platform_config(NMConnectivity *self,
         return NM_CONNECTIVITY_NONE;
     }
 
-    switch (addr_family) {
-    case AF_INET:
-    {
+    if (NM_IS_IPv4(addr_family)) {
         const NMPlatformIP4Route *route;
         gboolean                  found_global = FALSE;
         NMDedupMultiIter          iter;
@@ -1002,13 +1000,8 @@ check_platform_config(NMConnectivity *self,
             NM_SET_OUT(reason, "no global route configured");
             return NM_CONNECTIVITY_LIMITED;
         }
-        break;
-    }
-    case AF_INET6:
+    } else {
         /* Route scopes aren't meaningful for IPv6 so any route is fine. */
-        break;
-    default:
-        g_return_val_if_reached(FALSE);
     }
 
     NM_SET_OUT(reason, NULL);
@@ -1050,11 +1043,12 @@ nm_connectivity_check_start(NMConnectivity             *self,
     cb_data->concheck.con_config = _con_config_ref(priv->con_config);
 
     if (iface && ifindex > 0 && priv->enabled && priv->uri_valid) {
-        gboolean            has_systemd_resolved;
-        NMConnectivityState state;
-        const char         *reason;
+        gboolean has_systemd_resolved;
 
         if (platform) {
+            const char         *reason;
+            NMConnectivityState state;
+
             state = check_platform_config(self, platform, ifindex, addr_family, &reason);
             nm_assert((state == NM_CONNECTIVITY_UNKNOWN) == !reason);
             if (state != NM_CONNECTIVITY_UNKNOWN) {
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 28d9a788..5442efbf 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -468,7 +468,7 @@ _kc_invoke_callback(pid_t                   pid,
  * @log_name: for logging, the name of the processes to kill
  * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value
  * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter is ignored.
- * @callback: (allow-none): callback after the child terminated. This function will always
+ * @callback: (nullable): callback after the child terminated. This function will always
  *   be invoked asynchronously.
  * @user_data: passed on to callback
  *
@@ -507,16 +507,13 @@ nm_utils_kill_child_async(pid_t                   pid,
         return;
     } else if (ret != 0) {
         errsv = errno;
-        /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
-        if (errsv != ECHILD) {
-            nm_log_err(LOGD_CORE | log_domain,
-                       LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
-                       LOG_NAME_ARGS,
-                       nm_strerror_native(errsv),
-                       errsv);
-            _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1);
-            return;
-        }
+        nm_log_err(LOGD_CORE | log_domain,
+                   LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
+                   LOG_NAME_ARGS,
+                   nm_strerror_native(errsv),
+                   errsv);
+        _kc_invoke_callback(pid, log_domain, log_name, callback, user_data, FALSE, -1);
+        return;
     }
 
     /* send the first signal. */
@@ -601,7 +598,7 @@ _sleep_duration_convert_ms_to_us(guint32 sleep_duration_msec)
  * @log_domain: log debug information for this domain. Errors and warnings are logged both
  * as %LOGD_CORE and @log_domain.
  * @log_name: name of the process to kill for logging.
- * @child_status: (out) (allow-none): return the exit status of the child, if no error occurred.
+ * @child_status: (out) (optional): return the exit status of the child, if no error occurred.
  * @wait_before_kill_msec: Waittime in milliseconds before sending %SIGKILL signal. Set this value
  * to zero, not to send %SIGKILL. If @sig is already %SIGKILL, this parameter has not effect.
  * @sleep_duration_msec: the synchronous function sleeps repeatedly waiting for the child to terminate.
@@ -647,15 +644,12 @@ nm_utils_kill_child_sync(pid_t       pid,
         goto out;
     } else if (ret != 0) {
         errsv = errno;
-        /* ECHILD means, the process is not a child/does not exist or it has SIGCHILD blocked. */
-        if (errsv != ECHILD) {
-            nm_log_err(LOGD_CORE | log_domain,
-                       LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
-                       LOG_NAME_ARGS,
-                       nm_strerror_native(errsv),
-                       errsv);
-            goto out;
-        }
+        nm_log_err(LOGD_CORE | log_domain,
+                   LOG_NAME_FMT ": unexpected error while waitpid: %s (%d)",
+                   LOG_NAME_ARGS,
+                   nm_strerror_native(errsv),
+                   errsv);
+        goto out;
     }
 
     /* send first signal @sig */
@@ -1154,25 +1148,26 @@ nm_utils_read_link_absolute(const char *link_file, GError **error)
 #define MATCH_TAG_CONFIG_ENV            "env:"
 
 typedef struct {
-    const char *interface_name;
-    const char *device_type;
-    const char *driver;
-    const char *driver_version;
-    const char *dhcp_plugin;
+    /* This struct contains pre-processed data from NMMatchSpecDeviceData so
+     * we only need to parse it once. */
+    const NMMatchSpecDeviceData *data;
+    const char                  *device_type;
+    const char                  *driver;
+    const char                  *driver_version;
+    const char                  *dhcp_plugin;
     struct {
-        const char *value;
-        gboolean    is_parsed;
-        guint       len;
-        guint8      bin[_NM_UTILS_HWADDR_LEN_MAX];
+        gboolean is_parsed;
+        guint    len;
+        guint8   bin[_NM_UTILS_HWADDR_LEN_MAX];
     } hwaddr;
     struct {
-        const char *value;
-        gboolean    is_parsed;
-        guint32     a;
-        guint32     b;
-        guint32     c;
+        gboolean is_parsed;
+        gboolean is_good;
+        guint32  a;
+        guint32  b;
+        guint32  c;
     } s390_subchannels;
-} MatchDeviceData;
+} MatchSpecDeviceData;
 
 static gboolean
 match_device_s390_subchannels_parse(const char *s390_subchannels,
@@ -1240,22 +1235,25 @@ match_device_s390_subchannels_parse(const char *s390_subchannels,
 }
 
 static gboolean
-match_data_s390_subchannels_eval(const char *spec_str, MatchDeviceData *match_data)
+match_data_s390_subchannels_eval(const char *spec_str, MatchSpecDeviceData *match_data)
 {
-    guint32 a, b, c;
+    guint32 a;
+    guint32 b;
+    guint32 c;
 
     if (G_UNLIKELY(!match_data->s390_subchannels.is_parsed)) {
+        nm_assert(!match_data->s390_subchannels.is_good);
         match_data->s390_subchannels.is_parsed = TRUE;
 
-        if (!match_data->s390_subchannels.value
-            || !match_device_s390_subchannels_parse(match_data->s390_subchannels.value,
+        if (!match_data->data->s390_subchannels
+            || !match_device_s390_subchannels_parse(match_data->data->s390_subchannels,
                                                     &match_data->s390_subchannels.a,
                                                     &match_data->s390_subchannels.b,
                                                     &match_data->s390_subchannels.c)) {
-            match_data->s390_subchannels.value = NULL;
             return FALSE;
         }
-    } else if (!match_data->s390_subchannels.value)
+        match_data->s390_subchannels.is_good = TRUE;
+    } else if (!match_data->s390_subchannels.is_good)
         return FALSE;
 
     if (!match_device_s390_subchannels_parse(spec_str, &a, &b, &c))
@@ -1265,15 +1263,16 @@ match_data_s390_subchannels_eval(const char *spec_str, MatchDeviceData *match_da
 }
 
 static gboolean
-match_device_hwaddr_eval(const char *spec_str, MatchDeviceData *match_data)
+match_device_hwaddr_eval(const char *spec_str, MatchSpecDeviceData *match_data)
 {
     if (G_UNLIKELY(!match_data->hwaddr.is_parsed)) {
         match_data->hwaddr.is_parsed = TRUE;
+        nm_assert(match_data->hwaddr.len == 0);
 
-        if (match_data->hwaddr.value) {
+        if (match_data->data->hwaddr) {
             gsize l;
 
-            if (!_nm_utils_hwaddr_aton(match_data->hwaddr.value,
+            if (!_nm_utils_hwaddr_aton(match_data->data->hwaddr,
                                        match_data->hwaddr.bin,
                                        sizeof(match_data->hwaddr.bin),
                                        &l))
@@ -1281,7 +1280,7 @@ match_device_hwaddr_eval(const char *spec_str, MatchDeviceData *match_data)
             match_data->hwaddr.len = l;
         } else
             return FALSE;
-    } else if (!match_data->hwaddr.len)
+    } else if (match_data->hwaddr.len == 0)
         return FALSE;
 
     return nm_utils_hwaddr_matches(spec_str, -1, match_data->hwaddr.bin, match_data->hwaddr.len);
@@ -1336,7 +1335,7 @@ match_except(const char *spec_str, gboolean *out_except)
 }
 
 static gboolean
-match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *match_data)
+match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchSpecDeviceData *match_data)
 {
     if (spec_str[0] == '*' && spec_str[1] == '\0')
         return TRUE;
@@ -1359,10 +1358,10 @@ match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *m
             use_pattern = TRUE;
         }
 
-        if (match_data->interface_name) {
-            if (nm_streq(spec_str, match_data->interface_name))
+        if (match_data->data->interface_name) {
+            if (nm_streq(spec_str, match_data->data->interface_name))
                 return TRUE;
-            if (use_pattern && g_pattern_match_simple(spec_str, match_data->interface_name))
+            if (use_pattern && g_pattern_match_simple(spec_str, match_data->data->interface_name))
                 return TRUE;
         }
         return FALSE;
@@ -1408,7 +1407,8 @@ match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *m
     if (allow_fuzzy) {
         if (match_device_hwaddr_eval(spec_str, match_data))
             return TRUE;
-        if (match_data->interface_name && nm_streq(spec_str, match_data->interface_name))
+        if (match_data->data->interface_name
+            && nm_streq(spec_str, match_data->data->interface_name))
             return TRUE;
     }
 
@@ -1416,42 +1416,40 @@ match_device_eval(const char *spec_str, gboolean allow_fuzzy, MatchDeviceData *m
 }
 
 NMMatchSpecMatchType
-nm_match_spec_device(const GSList *specs,
-                     const char   *interface_name,
-                     const char   *device_type,
-                     const char   *driver,
-                     const char   *driver_version,
-                     const char   *hwaddr,
-                     const char   *s390_subchannels,
-                     const char   *dhcp_plugin)
-{
-    const GSList   *iter;
-    gboolean        has_match        = FALSE;
-    gboolean        has_match_except = FALSE;
-    gboolean        has_except       = FALSE;
-    gboolean        has_not_except   = FALSE;
-    const char     *spec_str;
-    MatchDeviceData match_data = {
-        .interface_name = interface_name,
-        .device_type    = nm_str_not_empty(device_type),
-        .driver         = nm_str_not_empty(driver),
-        .driver_version = nm_str_not_empty(driver_version),
-        .dhcp_plugin    = nm_str_not_empty(dhcp_plugin),
+nm_match_spec_device(const GSList *specs, const NMMatchSpecDeviceData *data)
+{
+    const GSList       *iter;
+    gboolean            has_match        = FALSE;
+    gboolean            has_match_except = FALSE;
+    gboolean            has_except       = FALSE;
+    gboolean            has_not_except   = FALSE;
+    const char         *spec_str;
+    MatchSpecDeviceData match_data;
+
+    nm_assert(data);
+    nm_assert(!data->hwaddr || nm_utils_hwaddr_valid(data->hwaddr, -1));
+
+    if (!specs)
+        return NM_MATCH_SPEC_NO_MATCH;
+
+    match_data = (MatchSpecDeviceData){
+        .data           = data,
+        .device_type    = nm_str_not_empty(data->device_type),
+        .driver         = nm_str_not_empty(data->driver),
+        .driver_version = nm_str_not_empty(data->driver_version),
+        .dhcp_plugin    = nm_str_not_empty(data->dhcp_plugin),
         .hwaddr =
             {
-                .value = hwaddr,
+                .is_parsed = FALSE,
+                .len       = 0,
             },
         .s390_subchannels =
             {
-                .value = s390_subchannels,
+                .is_parsed = FALSE,
+                .is_good   = FALSE,
             },
     };
 
-    nm_assert(!hwaddr || nm_utils_hwaddr_valid(hwaddr, -1));
-
-    if (!specs)
-        return NM_MATCH_SPEC_NO_MATCH;
-
     for (iter = specs; iter; iter = iter->next) {
         gboolean except;
 
@@ -1484,6 +1482,20 @@ nm_match_spec_device(const GSList *specs,
     return _match_result(has_except, has_not_except, has_match, has_match_except);
 }
 
+int
+nm_match_spec_match_type_to_bool(NMMatchSpecMatchType m, int no_match_value)
+{
+    switch (m) {
+    case NM_MATCH_SPEC_MATCH:
+        return TRUE;
+    case NM_MATCH_SPEC_NEG_MATCH:
+        return FALSE;
+    case NM_MATCH_SPEC_NO_MATCH:
+        return no_match_value;
+    }
+    return nm_assert_unreachable_val(no_match_value);
+}
+
 typedef struct {
     const char *uuid;
     const char *id;
@@ -3366,7 +3378,7 @@ nm_utils_stable_id_generated_complete(const char *stable_id_generated)
 }
 
 static void
-_stable_id_append(GString *str, const char *substitution)
+_stable_id_append(NMStrBuf *str, const char *substitution)
 {
     if (!substitution) {
         /* Would have been nicer to append "=NIL;" to differentiate between
@@ -3375,7 +3387,7 @@ _stable_id_append(GString *str, const char *substitution)
          * Can't do that now, as it would change behavior. */
         substitution = "";
     }
-    g_string_append_printf(str, "=%zu{%s}", strlen(substitution), substitution);
+    nm_str_buf_append_printf(str, "=%zu{%s}", strlen(substitution), substitution);
 }
 
 NMUtilsStableType
@@ -3386,8 +3398,9 @@ nm_utils_stable_id_parse(const char *stable_id,
                          const char *uuid,
                          char      **out_generated)
 {
-    gsize    i, idx_start;
-    GString *str = NULL;
+    nm_auto_str_buf NMStrBuf str = NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_232, FALSE);
+    gsize                    i;
+    gsize                    idx_start;
 
     g_return_val_if_fail(out_generated, NM_UTILS_STABLE_TYPE_RANDOM);
 
@@ -3396,6 +3409,14 @@ nm_utils_stable_id_parse(const char *stable_id,
         return NM_UTILS_STABLE_TYPE_UUID;
     }
 
+    if (nm_streq(stable_id, "default${CONNECTION}")) {
+        /* This changed behavior in 1.44. Explicitly setting "default${CONNECTION}"
+         * the same as the built-in default that we get by not configuring
+         * the property. */
+        *out_generated = NULL;
+        return NM_UTILS_STABLE_TYPE_UUID;
+    }
+
     /* the stable-id allows for some dynamic by performing text-substitutions
      * of ${...} patterns.
      *
@@ -3403,7 +3424,7 @@ nm_utils_stable_id_parse(const char *stable_id,
      * In contrast however, the process is unambiguous so that the resulting
      * effective id differs if:
      *  - the original, untranslated stable-id differs
-     *  - or any of the subsitutions differs.
+     *  - or any of the substitution differs.
      *
      * The reason for that is, for example if you specify "${CONNECTION}" in the
      * stable-id, then the resulting ID should be always(!) unique for this connection.
@@ -3440,28 +3461,26 @@ nm_utils_stable_id_parse(const char *stable_id,
             continue;
         }
 
-#define CHECK_PREFIX(prefix)                                                  \
-    ({                                                                        \
-        gboolean _match = FALSE;                                              \
-                                                                              \
-        if (NM_STR_HAS_PREFIX(&stable_id[i], "" prefix "")) {                 \
-            _match = TRUE;                                                    \
-            if (!str)                                                         \
-                str = g_string_sized_new(256);                                \
-            i += NM_STRLEN(prefix);                                           \
-            g_string_append_len(str, &(stable_id)[idx_start], i - idx_start); \
-            idx_start = i;                                                    \
-        }                                                                     \
-        _match;                                                               \
+#define CHECK_PREFIX(prefix)                                                     \
+    ({                                                                           \
+        gboolean _match = FALSE;                                                 \
+                                                                                 \
+        if (NM_STR_HAS_PREFIX(&stable_id[i], "" prefix "")) {                    \
+            _match = TRUE;                                                       \
+            i += NM_STRLEN(prefix);                                              \
+            nm_str_buf_append_len(&str, &(stable_id)[idx_start], i - idx_start); \
+            idx_start = i;                                                       \
+        }                                                                        \
+        _match;                                                                  \
     })
         if (CHECK_PREFIX("${CONNECTION}"))
-            _stable_id_append(str, uuid);
+            _stable_id_append(&str, uuid);
         else if (CHECK_PREFIX("${BOOT}"))
-            _stable_id_append(str, bootid);
+            _stable_id_append(&str, bootid);
         else if (CHECK_PREFIX("${DEVICE}"))
-            _stable_id_append(str, deviceid);
+            _stable_id_append(&str, deviceid);
         else if (CHECK_PREFIX("${MAC}"))
-            _stable_id_append(str, hwaddr);
+            _stable_id_append(&str, hwaddr);
         else if (g_str_has_prefix(&stable_id[i], "${RANDOM}")) {
             /* RANDOM makes not so much sense for cloned-mac-address
              * as the result is similar to specifying "cloned-mac-address=random".
@@ -3474,8 +3493,6 @@ nm_utils_stable_id_parse(const char *stable_id,
              * by toggling only the stable-id property of the connection.
              * With RANDOM being the most short-lived, ~non-stable~ variant.
              */
-            if (str)
-                g_string_free(str, TRUE);
             *out_generated = NULL;
             return NM_UTILS_STABLE_TYPE_RANDOM;
         } else {
@@ -3494,14 +3511,14 @@ nm_utils_stable_id_parse(const char *stable_id,
     }
 #undef CHECK_PREFIX
 
-    if (!str) {
+    if (str.len == 0) {
         *out_generated = NULL;
         return NM_UTILS_STABLE_TYPE_STABLE_ID;
     }
 
     if (idx_start < i)
-        g_string_append_len(str, &stable_id[idx_start], i - idx_start);
-    *out_generated = g_string_free(str, FALSE);
+        nm_str_buf_append_len(&str, &stable_id[idx_start], i - idx_start);
+    *out_generated = nm_str_buf_finalize(&str, NULL);
     return NM_UTILS_STABLE_TYPE_GENERATED;
 }
 
@@ -4877,25 +4894,25 @@ typedef struct {
     gsize    out_buffer_offset;
 } HelperInfo;
 
-#define _NMLOG_PREFIX_NAME "helper"
-#define _NMLOG_DOMAIN      LOGD_CORE
-#define _NMLOG2(level, info, ...)                                                   \
-    G_STMT_START                                                                    \
-    {                                                                               \
-        if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) {                         \
-            HelperInfo *_info = (info);                                             \
-                                                                                    \
-            _nm_log((level),                                                        \
-                    (_NMLOG_DOMAIN),                                                \
-                    0,                                                              \
-                    NULL,                                                           \
-                    NULL,                                                           \
-                    _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT                \
-                                       ",%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
-                    NM_HASH_OBFUSCATE_PTR(_info),                                   \
-                    _info->pid _NM_UTILS_MACRO_REST(__VA_ARGS__));                  \
-        }                                                                           \
-    }                                                                               \
+#define _NMLOG2_PREFIX_NAME "nm-daemon-helper"
+#define _NMLOG2_DOMAIN      LOGD_CORE
+#define _NMLOG2(level, info, ...)                                                    \
+    G_STMT_START                                                                     \
+    {                                                                                \
+        if (nm_logging_enabled((level), (_NMLOG2_DOMAIN))) {                         \
+            HelperInfo *_info = (info);                                              \
+                                                                                     \
+            _nm_log((level),                                                         \
+                    (_NMLOG2_DOMAIN),                                                \
+                    0,                                                               \
+                    NULL,                                                            \
+                    NULL,                                                            \
+                    _NMLOG2_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT                \
+                                        ",%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+                    NM_HASH_OBFUSCATE_PTR(_info),                                    \
+                    _info->pid _NM_UTILS_MACRO_REST(__VA_ARGS__));                   \
+        }                                                                            \
+    }                                                                                \
     G_STMT_END
 
 static void
@@ -4913,17 +4930,13 @@ helper_info_free(gpointer data)
     nm_clear_g_source_inst(&info->input_source);
     nm_clear_g_source_inst(&info->output_source);
     nm_clear_g_source_inst(&info->error_source);
-
-    if (info->child_stdout != -1)
-        nm_close(info->child_stdout);
-    if (info->child_stdin != -1)
-        nm_close(info->child_stdin);
-    if (info->child_stderr != -1)
-        nm_close(info->child_stderr);
+    nm_clear_fd(&info->child_stdout);
+    nm_clear_fd(&info->child_stdin);
+    nm_clear_fd(&info->child_stderr);
 
     if (info->pid != -1) {
         nm_assert(info->pid > 1);
-        nm_utils_kill_child_async(info->pid, SIGKILL, LOGD_CORE, _NMLOG_PREFIX_NAME, 0, NULL, NULL);
+        nm_utils_kill_child_async(info->pid, SIGKILL, LOGD_CORE, "nm-daemon-helper", 0, NULL, NULL);
     }
 
     g_free(info);
@@ -5015,8 +5028,7 @@ helper_have_data(int fd, GIOCondition condition, gpointer user_data)
         return G_SOURCE_CONTINUE;
 
     nm_clear_g_source_inst(&info->input_source);
-    nm_close(info->child_stdout);
-    info->child_stdout = -1;
+    nm_clear_fd(&info->child_stdout);
 
     _LOG2T(info, "stdout closed");
 
@@ -5044,9 +5056,7 @@ helper_have_err_data(int fd, GIOCondition condition, gpointer user_data)
         return G_SOURCE_CONTINUE;
 
     nm_clear_g_source_inst(&info->error_source);
-    nm_close(info->child_stderr);
-    info->child_stderr = -1;
-
+    nm_clear_fd(&info->child_stderr);
     return G_SOURCE_CONTINUE;
 }
 
@@ -5103,23 +5113,21 @@ nm_utils_spawn_helper(const char *const  *args,
     gs_free_error GError *error    = NULL;
     gs_free char         *commands = NULL;
     HelperInfo           *info;
-    int                   fd_flags;
     const char *const    *arg;
+    GMainContext         *context;
+    gsize                 n;
 
     nm_assert(args && args[0]);
 
     info  = g_new(HelperInfo, 1);
     *info = (HelperInfo){
-        .task         = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data),
-        .child_stdin  = -1,
-        .child_stdout = -1,
-        .pid          = -1,
+        .task = nm_g_task_new(NULL, cancellable, nm_utils_spawn_helper, callback, cb_data),
     };
 
     if (!g_spawn_async_with_pipes("/",
                                   (char **) NM_MAKE_STRV(LIBEXECDIR "/nm-daemon-helper"),
                                   (char **) NM_MAKE_STRV(),
-                                  G_SPAWN_DO_NOT_REAP_CHILD,
+                                  G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD,
                                   NULL,
                                   NULL,
                                   &info->pid,
@@ -5142,27 +5150,45 @@ nm_utils_spawn_helper(const char *const  *args,
 
     _LOG2D(info, "spawned process with args: %s", (commands = g_strjoinv(" ", (char **) args)));
 
-    info->child_watch_source = g_child_watch_source_new(info->pid);
-    g_source_set_callback(info->child_watch_source,
-                          G_SOURCE_FUNC(helper_child_terminated),
-                          info,
-                          NULL);
-    g_source_attach(info->child_watch_source, g_main_context_get_thread_default());
+    context = g_task_get_context(info->task);
+
+    /* The async function makes a lukewarm attempt to honor the current thread default
+     * context. However, it later uses nm_utils_kill_child_async() which always uses
+     * g_main_context_default(). For now, the function really can only be used with the
+     * main context. */
+    nm_assert(context == g_main_context_default());
+
+    /* We are using a GChildWatchSource in combination with kill()/waitpid()
+     * (where helper_info_free() clears the source and calls
+     * nm_utils_kill_child_async()). That leads to races where glib might have
+     * already reaped the process and our waitpid() call fails with:
+     *
+     *   <error> [TIMESTAMP] kill child process 'nm-daemon-helper' (PID): failed due to unexpected return value -1 by waitpid (No child processes, 10) after sending SIGKILL (9)
+     *
+     * This is a bug in glib, addressed by [1].  Maybe there should be a
+     * workaround here, and not using the child watcher?
+     *
+     * [1] https://gitlab.gnome.org/GNOME/glib/-/merge_requests/3353
+     */
+    info->child_watch_source = nm_g_child_watch_source_new(info->pid,
+                                                           G_PRIORITY_DEFAULT,
+                                                           helper_child_terminated,
+                                                           info,
+                                                           NULL);
+    g_source_attach(info->child_watch_source, context);
 
     info->timeout_source =
         nm_g_timeout_source_new_seconds(20, G_PRIORITY_DEFAULT, helper_timeout, info, NULL);
-    g_source_attach(info->timeout_source, g_main_context_get_thread_default());
+    g_source_attach(info->timeout_source, context);
 
-    /* Set file descriptors as non-blocking */
-    fd_flags = fcntl(info->child_stdin, F_GETFL, 0);
-    fcntl(info->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
-    fd_flags = fcntl(info->child_stdout, F_GETFL, 0);
-    fcntl(info->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
-    fd_flags = fcntl(info->child_stderr, F_GETFL, 0);
-    fcntl(info->child_stderr, F_SETFL, fd_flags | O_NONBLOCK);
+    nm_io_fcntl_setfl_update_nonblock(info->child_stdin);
+    nm_io_fcntl_setfl_update_nonblock(info->child_stdout);
+    nm_io_fcntl_setfl_update_nonblock(info->child_stderr);
 
     /* Watch process stdin */
-    info->out_buffer = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_40, TRUE);
+    for (n = 1, arg = args; *arg; arg++)
+        n += strlen(*arg) + 1u;
+    info->out_buffer = NM_STR_BUF_INIT(n, TRUE);
     for (arg = args; *arg; arg++) {
         nm_str_buf_append(&info->out_buffer, *arg);
         nm_str_buf_append_c(&info->out_buffer, '\0');
@@ -5173,7 +5199,7 @@ nm_utils_spawn_helper(const char *const  *args,
                                                   helper_can_write,
                                                   info,
                                                   NULL);
-    g_source_attach(info->output_source, g_main_context_get_thread_default());
+    g_source_attach(info->output_source, context);
 
     /* Watch process stdout */
     info->in_buffer    = NM_STR_BUF_INIT(0, FALSE);
@@ -5183,7 +5209,7 @@ nm_utils_spawn_helper(const char *const  *args,
                                                  helper_have_data,
                                                  info,
                                                  NULL);
-    g_source_attach(info->input_source, g_main_context_get_thread_default());
+    g_source_attach(info->input_source, context);
 
     /* Watch process stderr */
     info->err_buffer   = NM_STR_BUF_INIT(0, FALSE);
@@ -5193,7 +5219,7 @@ nm_utils_spawn_helper(const char *const  *args,
                                                  helper_have_err_data,
                                                  info,
                                                  NULL);
-    g_source_attach(info->error_source, g_main_context_get_thread_default());
+    g_source_attach(info->error_source, context);
 
     if (cancellable) {
         gulong signal_id;
diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h
index bc936496..55112504 100644
--- a/src/core/nm-core-utils.h
+++ b/src/core/nm-core-utils.h
@@ -193,14 +193,20 @@ typedef enum {
     NM_MATCH_SPEC_NEG_MATCH = 2,
 } NMMatchSpecMatchType;
 
-NMMatchSpecMatchType nm_match_spec_device(const GSList *specs,
-                                          const char   *interface_name,
-                                          const char   *device_type,
-                                          const char   *driver,
-                                          const char   *driver_version,
-                                          const char   *hwaddr,
-                                          const char   *s390_subchannels,
-                                          const char   *dhcp_plugin);
+int nm_match_spec_match_type_to_bool(NMMatchSpecMatchType m, int no_match_value);
+
+typedef struct _NMMatchSpecDeviceData {
+    const char *interface_name;
+    const char *device_type;
+    const char *driver;
+    const char *driver_version;
+    const char *dhcp_plugin;
+    const char *hwaddr;
+    const char *s390_subchannels;
+} NMMatchSpecDeviceData;
+
+NMMatchSpecMatchType nm_match_spec_device(const GSList *specs, const NMMatchSpecDeviceData *data);
+
 NMMatchSpecMatchType nm_match_spec_config(const GSList *specs, guint nm_version, const char *env);
 GSList              *nm_match_spec_split(const char *value);
 char                *nm_match_spec_join(GSList *specs);
@@ -262,18 +268,17 @@ _nmtst_auto_utils_host_id_context_pop(const char *const *unused)
     nmtst_utils_host_id_pop();
 }
 
-#define _NMTST_UTILS_HOST_ID_CONTEXT(uniq, host_id)                                        \
-    _nm_unused nm_auto(_nmtst_auto_utils_host_id_context_pop) const char *const NM_UNIQ_T( \
-        _host_id_context_,                                                                 \
-        uniq) = ({                                                                         \
-        const gint64 _timestamp_ns = 1631000672;                                           \
-                                                                                           \
-        nmtst_utils_host_id_push((const guint8 *) "" host_id "",                           \
-                                 NM_STRLEN(host_id),                                       \
-                                 TRUE,                                                     \
-                                 &_timestamp_ns);                                          \
-        "" host_id "";                                                                     \
-    })
+#define _NMTST_UTILS_HOST_ID_CONTEXT(uniq, host_id)                      \
+    _nm_unused            nm_auto(_nmtst_auto_utils_host_id_context_pop) \
+        const char *const NM_UNIQ_T(_host_id_context_, uniq) = ({        \
+            const gint64 _timestamp_ns = 1631000672;                     \
+                                                                         \
+            nmtst_utils_host_id_push((const guint8 *) "" host_id "",     \
+                                     NM_STRLEN(host_id),                 \
+                                     TRUE,                               \
+                                     &_timestamp_ns);                    \
+            "" host_id "";                                               \
+        })
 
 #define NMTST_UTILS_HOST_ID_CONTEXT(host_id) _NMTST_UTILS_HOST_ID_CONTEXT(NM_UNIQ, host_id)
 
diff --git a/src/core/nm-dbus-manager.c b/src/core/nm-dbus-manager.c
index af7de8c4..0bde5971 100644
--- a/src/core/nm-dbus-manager.c
+++ b/src/core/nm-dbus-manager.c
@@ -1410,48 +1410,23 @@ nm_dbus_manager_start(NMDBusManager                  *self,
 }
 
 gboolean
-nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name)
+nm_dbus_manager_request_name_sync(NMDBusManager *self)
 {
     NMDBusManagerPrivate      *priv;
     gs_free_error GError      *error = NULL;
     gs_unref_variant GVariant *ret   = NULL;
     guint32                    result;
-    guint                      registration_id;
 
     g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), FALSE);
 
     priv = NM_DBUS_MANAGER_GET_PRIVATE(self);
 
-    /* Create the D-Bus connection and registering the name synchronously.
-     * That is necessary because we need to exit right away if we can't
-     * acquire the name despite connecting to the bus successfully.
-     * It means that something is gravely broken -- such as another NetworkManager
-     * instance running. */
-    priv->main_dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
-    if (!priv->main_dbus_connection) {
-        _LOGE("cannot connect to D-Bus: %s", error->message);
-        return FALSE;
-    }
-
-    g_dbus_connection_set_exit_on_close(priv->main_dbus_connection, FALSE);
-
-    if (!request_name) {
-        _LOGD("D-Bus connection created");
+    if (priv->objmgr_registration_id == 0) {
+        /* Do nothing. We're presumably in the configure-and-quit mode. */
         return TRUE;
     }
 
-    registration_id = g_dbus_connection_register_object(
-        priv->main_dbus_connection,
-        OBJECT_MANAGER_SERVER_BASE_PATH,
-        NM_UNCONST_PTR(GDBusInterfaceInfo, &interface_info_objmgr),
-        &dbus_vtable_objmgr,
-        self,
-        NULL,
-        &error);
-    if (!registration_id) {
-        _LOGE("failure to register object manager: %s", error->message);
-        return FALSE;
-    }
+    g_return_val_if_fail(G_IS_DBUS_CONNECTION(priv->main_dbus_connection), FALSE);
 
     ret = g_dbus_connection_call_sync(
         priv->main_dbus_connection,
@@ -1465,12 +1440,12 @@ nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name)
         -1,
         NULL,
         &error);
+
     if (!ret) {
         _LOGE("fatal failure to acquire D-Bus service \"%s"
               ": %s",
               NM_DBUS_SERVICE,
               error->message);
-        g_dbus_connection_unregister_object(priv->main_dbus_connection, registration_id);
         return FALSE;
     }
 
@@ -1479,13 +1454,55 @@ nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name)
         _LOGE("fatal failure to acquire D-Bus service \"%s\" (%u). Service already taken",
               NM_DBUS_SERVICE,
               (guint) result);
-        g_dbus_connection_unregister_object(priv->main_dbus_connection, registration_id);
+        return FALSE;
+    }
+
+    _LOGI("acquired D-Bus service \"%s\"", NM_DBUS_SERVICE);
+    return TRUE;
+}
+
+gboolean
+nm_dbus_manager_setup(NMDBusManager *self)
+{
+    NMDBusManagerPrivate *priv;
+    gs_free_error GError *error = NULL;
+    guint                 registration_id;
+
+    g_return_val_if_fail(NM_IS_DBUS_MANAGER(self), FALSE);
+
+    priv = NM_DBUS_MANAGER_GET_PRIVATE(self);
+
+    g_return_val_if_fail(!priv->main_dbus_connection, FALSE);
+
+    /* Create the D-Bus connection and registering the name synchronously.
+     * That is necessary because we need to exit right away if we can't
+     * acquire the name despite connecting to the bus successfully.
+     * It means that something is gravely broken -- such as another NetworkManager
+     * instance running. */
+    priv->main_dbus_connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
+    if (!priv->main_dbus_connection) {
+        _LOGE("cannot connect to D-Bus: %s", error->message);
+        return FALSE;
+    }
+
+    g_dbus_connection_set_exit_on_close(priv->main_dbus_connection, FALSE);
+
+    registration_id = g_dbus_connection_register_object(
+        priv->main_dbus_connection,
+        OBJECT_MANAGER_SERVER_BASE_PATH,
+        NM_UNCONST_PTR(GDBusInterfaceInfo, &interface_info_objmgr),
+        &dbus_vtable_objmgr,
+        self,
+        NULL,
+        &error);
+    if (!registration_id) {
+        _LOGE("failure to register object manager: %s", error->message);
         return FALSE;
     }
 
     priv->objmgr_registration_id = registration_id;
 
-    _LOGI("acquired D-Bus service \"%s\"", NM_DBUS_SERVICE);
+    _LOGD("D-Bus connection created and ObjectManager object registered");
 
     return TRUE;
 }
diff --git a/src/core/nm-dbus-manager.h b/src/core/nm-dbus-manager.h
index b68161db..078dbdd2 100644
--- a/src/core/nm-dbus-manager.h
+++ b/src/core/nm-dbus-manager.h
@@ -37,7 +37,9 @@ typedef void (*NMDBusManagerSetPropertyHandler)(NMDBusObject
                                                 GVariant                          *value,
                                                 gpointer                           user_data);
 
-gboolean nm_dbus_manager_acquire_bus(NMDBusManager *self, gboolean request_name);
+gboolean nm_dbus_manager_setup(NMDBusManager *self);
+
+gboolean nm_dbus_manager_request_name_sync(NMDBusManager *self);
 
 GDBusConnection *nm_dbus_manager_get_dbus_connection(NMDBusManager *self);
 
diff --git a/src/core/nm-firewall-utils.c b/src/core/nm-firewall-utils.c
index 03f1a9a5..45dab093 100644
--- a/src/core/nm-firewall-utils.c
+++ b/src/core/nm-firewall-utils.c
@@ -74,7 +74,7 @@ _nft_ifname_valid(const char *str)
             return NULL;
         }
     }
-    if (i >= NMP_IFNAMSIZ)
+    if (i >= NM_IFNAMSIZ)
         return NULL;
 
     return str;
@@ -154,10 +154,10 @@ _share_iptables_get_name(gboolean is_iptables_chain, const char *prefix, const c
     nm_str_buf_append(&strbuf, prefix);
 
     ip_iface_len = strlen(ip_iface);
-    G_STATIC_ASSERT_EXPR(NMP_IFNAMSIZ == 16);
-    if (ip_iface_len >= NMP_IFNAMSIZ) {
+    G_STATIC_ASSERT_EXPR(NM_IFNAMSIZ == 16);
+    if (ip_iface_len >= NM_IFNAMSIZ) {
         nm_assert_not_reached();
-        ip_iface_len = NMP_IFNAMSIZ - 1;
+        ip_iface_len = NM_IFNAMSIZ - 1;
     }
 
     if (NM_STRCHAR_ALL(ip_iface,
@@ -763,13 +763,15 @@ nm_firewall_nft_stdio_mlag(gboolean           up,
                            const char        *bond_ifname,
                            const char *const *bond_ifnames_down,
                            const char *const *active_members,
-                           const char *const *previous_members)
+                           const char *const *previous_members,
+                           gboolean           with_counters)
 {
     nm_auto_str_buf NMStrBuf strbuf_table_name =
         NM_STR_BUF_INIT_A(NM_UTILS_GET_NEXT_REALLOC_SIZE_32, FALSE);
     nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
     const char              *table_name;
     gsize                    i;
+    const char *const        s_counter = with_counters ? " counter" : "";
 
     if (NM_MORE_ASSERTS > 10 && active_members) {
         /* No duplicates. We make certain assumptions here, and we don't
@@ -876,9 +878,10 @@ nm_firewall_nft_stdio_mlag(gboolean           up,
             _append(&strbuf,
                     "add rule netdev %s %s pkttype {"
                     " broadcast, multicast "
-                    "} counter drop",
+                    "}%s drop",
                     table_name,
-                    chain_name);
+                    chain_name,
+                    s_counter);
         }
 
         /* OVS SLB rule 2
@@ -905,15 +908,17 @@ nm_firewall_nft_stdio_mlag(gboolean           up,
                 table_name,
                 bond_ifname);
         _append(&strbuf,
-                "add rule netdev %s tx-snoop-source-mac set update ether saddr . vlan id"
-                " timeout 5s @macset-tagged counter return"
+                "add rule netdev %s tx-snoop-source-mac set update ether saddr . vlan id "
+                "timeout 5s @macset-tagged%s return"
                 "", /* tagged */
-                table_name);
+                table_name,
+                s_counter);
         _append(&strbuf,
-                "add rule netdev %s tx-snoop-source-mac set update ether saddr"
-                " timeout 5s @macset-untagged counter"
+                "add rule netdev %s tx-snoop-source-mac set update ether saddr timeout 5s "
+                "@macset-untagged%s"
                 "", /* untagged*/
-                table_name);
+                table_name,
+                s_counter);
 
         _append(&strbuf,
                 "add chain netdev %s rx-drop-looped-packets {"
@@ -921,18 +926,20 @@ nm_firewall_nft_stdio_mlag(gboolean           up,
                 "}",
                 table_name,
                 bond_ifname);
+        _append(
+            &strbuf,
+            "add rule netdev %s rx-drop-looped-packets ether saddr . vlan id @macset-tagged%s drop",
+            table_name,
+            s_counter);
         _append(&strbuf,
-                "add rule netdev %s rx-drop-looped-packets ether saddr . vlan id"
-                " @macset-tagged counter drop",
-                table_name);
-        _append(&strbuf,
-                "add rule netdev %s rx-drop-looped-packets ether type vlan counter return"
+                "add rule netdev %s rx-drop-looped-packets ether type vlan%s return"
                 "", /* avoid looking up tagged packets in untagged table */
-                table_name);
+                table_name,
+                s_counter);
         _append(&strbuf,
-                "add rule netdev %s rx-drop-looped-packets ether saddr @macset-untagged"
-                " counter drop",
-                table_name);
+                "add rule netdev %s rx-drop-looped-packets ether saddr @macset-untagged%s drop",
+                table_name,
+                s_counter);
     }
 
 out:
diff --git a/src/core/nm-firewall-utils.h b/src/core/nm-firewall-utils.h
index ca138ccf..9f13a512 100644
--- a/src/core/nm-firewall-utils.h
+++ b/src/core/nm-firewall-utils.h
@@ -39,6 +39,7 @@ GBytes *nm_firewall_nft_stdio_mlag(gboolean           up,
                                    const char        *bond_ifname,
                                    const char *const *bond_ifnames_down,
                                    const char *const *active_members,
-                                   const char *const *previous_members);
+                                   const char *const *previous_members,
+                                   gboolean           with_counters);
 
 #endif /* __NM_FIREWALL_UTILS_H__ */
diff --git a/src/core/nm-keep-alive.c b/src/core/nm-keep-alive.c
index e147163c..3ab5c36e 100644
--- a/src/core/nm-keep-alive.c
+++ b/src/core/nm-keep-alive.c
@@ -364,7 +364,7 @@ nm_keep_alive_disarm(NMKeepAlive *self)
 
 /**
  * nm_keep_alive_destroy:
- * @self: (allow-none): the #NMKeepAlive instance to destroy.
+ * @self: (nullable): the #NMKeepAlive instance to destroy.
  *
  * This does 3 things in one:
  *
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index d5dedb9c..96274ba9 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -2606,7 +2606,6 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData       *self,
                                               int                   addr_family,
                                               guint32               route_table,
                                               guint32               route_metric,
-                                              gboolean              force_commit,
                                               const NML3ConfigData *source)
 {
     const int          IS_IPv4 = NM_IS_IPv4(addr_family);
@@ -2651,7 +2650,6 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData       *self,
                                                                                     self->ifindex,
                                                                                     route_table,
                                                                                     route_metric,
-                                                                                    force_commit,
                                                                                     &r_stack.r4);
             if (r)
                 nm_l3_config_data_add_route(self, addr_family, NULL, r);
@@ -2687,13 +2685,12 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData       *self,
                 }
 
                 rx.r6 = (NMPlatformIP6Route){
-                    .ifindex        = self->ifindex,
-                    .rt_source      = NM_IP_CONFIG_SOURCE_KERNEL,
-                    .table_coerced  = nm_platform_route_table_coerce(route_table),
-                    .metric         = route_metric,
-                    .network        = *a6,
-                    .plen           = plen,
-                    .r_force_commit = force_commit,
+                    .ifindex       = self->ifindex,
+                    .rt_source     = NM_IP_CONFIG_SOURCE_KERNEL,
+                    .table_coerced = nm_platform_route_table_coerce(route_table),
+                    .metric        = route_metric,
+                    .network       = *a6,
+                    .plen          = plen,
                 };
 
                 nm_platform_ip_route_normalize(addr_family, &rx.rx);
@@ -3199,7 +3196,6 @@ nm_l3_config_data_merge(NML3ConfigData       *self,
             NMPlatformIPXAddress       a;
             NML3ConfigMergeHookResult  hook_result = {
                  .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT,
-                 .force_commit     = NM_OPTION_BOOL_DEFAULT,
             };
 
 #define _ensure_a()                                       \
@@ -3232,12 +3228,6 @@ nm_l3_config_data_merge(NML3ConfigData       *self,
                 a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready);
             }
 
-            if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT
-                && (!!hook_result.force_commit) != a_src->a_force_commit) {
-                _ensure_a();
-                a.ax.a_force_commit = (!!hook_result.force_commit);
-            }
-
             nm_l3_config_data_add_address_full(self,
                                                addr_family,
                                                a_src == &a.ax ? NULL : obj,
@@ -3257,7 +3247,6 @@ nm_l3_config_data_merge(NML3ConfigData       *self,
                 NMPlatformIPXRoute        r;
                 NML3ConfigMergeHookResult hook_result = {
                     .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT,
-                    .force_commit     = NM_OPTION_BOOL_DEFAULT,
                 };
 
 #define _ensure_r()                                     \
@@ -3283,12 +3272,6 @@ nm_l3_config_data_merge(NML3ConfigData       *self,
                     r.rx.ifindex = self->ifindex;
                 }
 
-                if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT
-                    && (!!hook_result.force_commit) != r_src->r_force_commit) {
-                    _ensure_r();
-                    r.rx.r_force_commit = (!!hook_result.force_commit);
-                }
-
                 if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) {
                     if (r_src->table_any) {
                         _ensure_r();
diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h
index bfab04d9..80abb00d 100644
--- a/src/core/nm-l3-config-data.h
+++ b/src/core/nm-l3-config-data.h
@@ -137,7 +137,6 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex        *mu
 
 typedef struct {
     NMOptionBool ip4acd_not_ready;
-    NMOptionBool force_commit;
 } NML3ConfigMergeHookResult;
 
 typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData      *l3cd,
@@ -164,7 +163,6 @@ void nm_l3_config_data_add_dependent_device_routes(NML3ConfigData       *self,
                                                    int                   addr_family,
                                                    guint32               route_table,
                                                    guint32               route_metric,
-                                                   gboolean              force_commit,
                                                    const NML3ConfigData *source);
 
 /*****************************************************************************/
diff --git a/src/core/nm-l3-ipv6ll.c b/src/core/nm-l3-ipv6ll.c
index 0133ebe6..38aa98fc 100644
--- a/src/core/nm-l3-ipv6ll.c
+++ b/src/core/nm-l3-ipv6ll.c
@@ -420,9 +420,7 @@ _lladdr_handle_changed(NML3IPv6LL *self, gboolean force_commit)
                                 NM_DNS_PRIORITY_DEFAULT_NORMAL,
                                 NM_L3_ACD_DEFEND_TYPE_ALWAYS,
                                 0,
-                                /* Even if the address was removed from platform, it must
-                                 * be re-added, hence FORCE_ONCE. */
-                                NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
+                                NM_L3CFG_CONFIG_FLAGS_NONE,
                                 NM_L3_CONFIG_MERGE_FLAGS_NONE))
             changed = TRUE;
     } else {
@@ -667,17 +665,13 @@ _nm_l3_ipv6ll_new(NML3Cfg                  *l3cfg,
     };
 
     if (self->addrgen.stable_type == NM_UTILS_STABLE_TYPE_NONE) {
-        char sbuf_token[sizeof(self->addrgen.token.iid) * 3];
+        char sbuf_token[INET6_ADDRSTRLEN];
 
         self->addrgen.token.iid = *token_iid;
         _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT ", ifindex=%d, token=%s%s",
               NM_HASH_OBFUSCATE_PTR(l3cfg),
               nm_l3cfg_get_ifindex(l3cfg),
-              nm_utils_bin2hexstr_full(&self->addrgen.token.iid,
-                                       sizeof(self->addrgen.token.iid),
-                                       ':',
-                                       FALSE,
-                                       sbuf_token),
+              nm_utils_inet6_interface_identifier_to_token(&self->addrgen.token.iid, sbuf_token),
               self->assume ? ", assume" : "");
     } else {
         self->addrgen.stable_privacy.ifname     = g_strdup(ifname);
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c
index a49654fe..3c2d3ec8 100644
--- a/src/core/nm-l3cfg.c
+++ b/src/core/nm-l3cfg.c
@@ -11,6 +11,7 @@
 #include <linux/if_ether.h>
 #include <linux/rtnetlink.h>
 
+#include "libnm-glib-aux/nm-prioq.h"
 #include "libnm-glib-aux/nm-time-utils.h"
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nmp-object.h"
@@ -123,25 +124,34 @@ typedef struct {
 
     CList os_lst;
 
-    /* If we have a timeout pending, we link the instance to
-     * self->priv.p->obj_state_temporary_not_available_lst_head. */
-    CList os_temporary_not_available_lst;
-
     /* If a NMPObject is no longer to be configured (but was configured
      * during a previous commit), then we need to remember it so that the
      * next commit can delete the address/route in kernel. It becomes a zombie. */
     CList os_zombie_lst;
 
-    /* We might want to configure "obj" in platform, but it's currently not possible.
-     * For example, certain IPv6 routes can only be added after the IPv6 address
-     * becomes non-tentative (*sigh*). In such a case, we need to remember that, and
-     * retry later. If this timestamp is set to a non-zero value, then it means
-     * we tried to configure the obj (at that timestamp) and failed, but we are
-     * waiting to retry.
+    /* Used by _handle_routes_failed() mechanism. If "os_plobj" is set, then
+     * this is meaningless but should be set to zero.
+     *
+     * If set to a non-zero value, this means adding the object failed.  Until
+     * "os_failedobj_expiry_msec" we are still waiting whether we would be able to
+     * configure the object. Afterwards, we consider the element failed.
      *
-     * See also self->priv.p->obj_state_temporary_not_available_lst_head
-     * and self->priv.p->obj_state_temporary_not_available_timeout_source. */
-    gint64 os_temporary_not_available_timestamp_msec;
+     * Depending on "os_failedobj_prioq_idx", we are currently waiting whether the
+     * condition can resolve itself or becomes a failure. */
+    gint64 os_failedobj_expiry_msec;
+
+    /* The index into the "priv->failedobj_prioq" queue for objects that are failed.
+     * - this field is meaningless in case "os_plobj" is set (but it should be
+     *   set to NM_PRIOQ_IDX_NULL).
+     * - otherwise, if "os_failedobj_expiry_msec" is 0, no error was detected so
+     *   far. The index should be set to NM_PRIOQ_IDX_NULL.
+     * - otherwise, if the index is NM_PRIOQ_IDX_NULL it means that the object
+     *   is not tracked by the queue, no grace timer is pending, and the object
+     *   is considered failed.
+     * - otherwise, the index is used for tracking the element in the queue.
+     *   It means, we are currently waiting to decide whether this will be a
+     *   failure or not. */
+    guint os_failedobj_prioq_idx;
 
     /* When the obj is a zombie (that means, it was previously configured by NML3Cfg, but
      * now no longer), it needs to be deleted from platform. This ratelimits the time
@@ -206,7 +216,6 @@ typedef struct {
     guint32           acd_timeout_msec_confdata;
     NML3AcdDefendType acd_defend_type_confdata : 3;
     bool              dirty_confdata : 1;
-    gboolean          force_commit_once : 1;
 } L3ConfigData;
 
 struct _NML3CfgBlockHandle {
@@ -241,7 +250,6 @@ typedef struct _NML3CfgPrivate {
 
     CList obj_state_lst_head;
     CList obj_state_zombie_lst_head;
-    CList obj_state_temporary_not_available_lst_head;
 
     GHashTable *acd_ipv4_addresses_on_link;
 
@@ -288,12 +296,22 @@ typedef struct _NML3CfgPrivate {
 
     guint64 pseudo_timestamp_counter;
 
-    GSource *obj_state_temporary_not_available_timeout_source;
+    NMPrioq  failedobj_prioq;
+    GSource *failedobj_timeout_source;
+    gint64   failedobj_timeout_expiry_msec;
 
     NML3CfgCommitType commit_on_idle_type;
 
     gint8 commit_reentrant_count;
 
+    union {
+        struct {
+            gint8 commit_reentrant_count_ip_address_sync_6;
+            gint8 commit_reentrant_count_ip_address_sync_4;
+        };
+        gint8 commit_reentrant_count_ip_address_sync_x[2];
+    };
+
     /* The value that was set before we touched the sysctl (this only is
      * meaningful if "ip6_privacy_set" is true. At the end, we want to restore
      * this value. */
@@ -340,6 +358,9 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT)
 
 #define _MPTCP_TAG(self, IS_IPv4) ((gconstpointer) (&(((const char *) (self))[2 + (!(IS_IPv4))])))
 
+#define _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family) \
+    ((gconstpointer) & (((char *) self)[1 + NM_IS_IPv4(addr_family)]))
+
 /*****************************************************************************/
 
 #define _NMLOG_DOMAIN      LOGD_CORE
@@ -410,8 +431,6 @@ static NM_UTILS_ENUM2STR_DEFINE(
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, "pre-commit"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"),
-    NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED,
-                      "routes-temporary-not-available-expired"),
     NM_UTILS_ENUM2STR_IGNORE(_NM_L3_CONFIG_NOTIFY_TYPE_NUM), );
 
 static NM_UTILS_ENUM2STR_DEFINE(_l3_acd_defend_type_to_string,
@@ -754,51 +773,51 @@ _nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gp
 
 /*****************************************************************************/
 
-#define nm_assert_obj_state(self, obj_state)                                                      \
-    G_STMT_START                                                                                  \
-    {                                                                                             \
-        if (NM_MORE_ASSERTS > 0) {                                                                \
-            const NML3Cfg      *_self      = (self);                                              \
-            const ObjStateData *_obj_state = (obj_state);                                         \
-                                                                                                  \
-            nm_assert(_obj_state);                                                                \
-            nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj),                             \
-                                NMP_OBJECT_TYPE_IP4_ADDRESS,                                      \
-                                NMP_OBJECT_TYPE_IP6_ADDRESS,                                      \
-                                NMP_OBJECT_TYPE_IP4_ROUTE,                                        \
-                                NMP_OBJECT_TYPE_IP6_ROUTE));                                      \
-            nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform);                   \
-            nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0)                \
-                      == c_list_is_empty(&_obj_state->os_temporary_not_available_lst));           \
-            if (_self) {                                                                          \
-                if (c_list_is_empty(&_obj_state->os_zombie_lst)) {                                \
-                    nm_assert(_self->priv.p->combined_l3cd_commited);                             \
-                                                                                                  \
-                    if (NM_MORE_ASSERTS > 5) {                                                    \
-                        nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head,             \
-                                                  &_obj_state->os_lst));                          \
-                        nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0)    \
-                                  || c_list_contains(                                             \
-                                      &_self->priv.p->obj_state_temporary_not_available_lst_head, \
-                                      &_obj_state->os_temporary_not_available_lst));              \
-                        nm_assert(_obj_state->os_plobj                                            \
-                                  == nm_platform_lookup_obj(_self->priv.platform,                 \
-                                                            NMP_CACHE_ID_TYPE_OBJECT_TYPE,        \
-                                                            _obj_state->obj));                    \
-                        nm_assert(                                                                \
-                            c_list_is_empty(&obj_state->os_zombie_lst)                            \
-                                ? (_obj_state->obj                                                \
-                                   == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj(  \
-                                       _self->priv.p->combined_l3cd_commited,                     \
-                                       _obj_state->obj)))                                         \
-                                : (!nm_l3_config_data_lookup_obj(                                 \
-                                    _self->priv.p->combined_l3cd_commited,                        \
-                                    _obj_state->obj)));                                           \
-                    }                                                                             \
-                }                                                                                 \
-            }                                                                                     \
-        }                                                                                         \
-    }                                                                                             \
+#define nm_assert_obj_state(self, obj_state)                                                     \
+    G_STMT_START                                                                                 \
+    {                                                                                            \
+        if (NM_MORE_ASSERTS > 0) {                                                               \
+            const NML3Cfg      *_self      = (self);                                             \
+            const ObjStateData *_obj_state = (obj_state);                                        \
+                                                                                                 \
+            nm_assert(_obj_state);                                                               \
+            nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj),                            \
+                                NMP_OBJECT_TYPE_IP4_ADDRESS,                                     \
+                                NMP_OBJECT_TYPE_IP6_ADDRESS,                                     \
+                                NMP_OBJECT_TYPE_IP4_ROUTE,                                       \
+                                NMP_OBJECT_TYPE_IP6_ROUTE));                                     \
+            nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform);                  \
+            nm_assert(_obj_state->os_failedobj_expiry_msec != 0                                  \
+                      || _obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);               \
+            nm_assert(_obj_state->os_failedobj_expiry_msec == 0 || !_obj_state->os_plobj);       \
+            nm_assert(_obj_state->os_failedobj_expiry_msec == 0                                  \
+                      || c_list_is_empty(&_obj_state->os_zombie_lst));                           \
+            nm_assert(_obj_state->os_failedobj_expiry_msec == 0 || _obj_state->obj);             \
+            if (_self) {                                                                         \
+                if (c_list_is_empty(&_obj_state->os_zombie_lst)) {                               \
+                    nm_assert(_self->priv.p->combined_l3cd_commited);                            \
+                                                                                                 \
+                    if (NM_MORE_ASSERTS > 5) {                                                   \
+                        nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head,            \
+                                                  &_obj_state->os_lst));                         \
+                        nm_assert(_obj_state->os_plobj                                           \
+                                  == nm_platform_lookup_obj(_self->priv.platform,                \
+                                                            NMP_CACHE_ID_TYPE_OBJECT_TYPE,       \
+                                                            _obj_state->obj));                   \
+                        nm_assert(                                                               \
+                            c_list_is_empty(&obj_state->os_zombie_lst)                           \
+                                ? (_obj_state->obj                                               \
+                                   == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj( \
+                                       _self->priv.p->combined_l3cd_commited,                    \
+                                       _obj_state->obj)))                                        \
+                                : (!nm_l3_config_data_lookup_obj(                                \
+                                    _self->priv.p->combined_l3cd_commited,                       \
+                                    _obj_state->obj)));                                          \
+                    }                                                                            \
+                }                                                                                \
+            }                                                                                    \
+        }                                                                                        \
+    }                                                                                            \
     G_STMT_END
 
 static ObjStateData *
@@ -808,13 +827,14 @@ _obj_state_data_new(const NMPObject *obj, const NMPObject *plobj)
 
     obj_state  = g_slice_new(ObjStateData);
     *obj_state = (ObjStateData){
-        .obj                            = nmp_object_ref(obj),
-        .os_plobj                       = nmp_object_ref(plobj),
-        .os_was_in_platform             = !!plobj,
-        .os_nm_configured               = FALSE,
-        .os_dirty                       = FALSE,
-        .os_temporary_not_available_lst = C_LIST_INIT(obj_state->os_temporary_not_available_lst),
-        .os_zombie_lst                  = C_LIST_INIT(obj_state->os_zombie_lst),
+        .obj                      = nmp_object_ref(obj),
+        .os_plobj                 = nmp_object_ref(plobj),
+        .os_was_in_platform       = !!plobj,
+        .os_nm_configured         = FALSE,
+        .os_dirty                 = FALSE,
+        .os_failedobj_expiry_msec = 0,
+        .os_failedobj_prioq_idx   = NM_PRIOQ_IDX_NULL,
+        .os_zombie_lst            = C_LIST_INIT(obj_state->os_zombie_lst),
     };
     return obj_state;
 }
@@ -824,9 +844,10 @@ _obj_state_data_free(gpointer data)
 {
     ObjStateData *obj_state = data;
 
+    nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
+
     c_list_unlink_stale(&obj_state->os_lst);
     c_list_unlink_stale(&obj_state->os_zombie_lst);
-    c_list_unlink_stale(&obj_state->os_temporary_not_available_lst);
     nmp_object_unref(obj_state->obj);
     nmp_object_unref(obj_state->os_plobj);
     nm_g_slice_free(obj_state);
@@ -864,15 +885,17 @@ _obj_state_data_to_string(const ObjStateData *obj_state, char *buf, gsize buf_si
     } else if (obj_state->os_was_in_platform)
         nm_strbuf_append_str(&buf, &buf_size, ", was-in-platform");
 
-    if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
+    if (obj_state->os_failedobj_expiry_msec > 0) {
         nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
-        nm_strbuf_append(
-            &buf,
-            &buf_size,
-            ", temporary-not-available-since=%" G_GINT64_FORMAT ".%03d",
-            (now_msec - obj_state->os_temporary_not_available_timestamp_msec) / 1000,
-            (int) ((now_msec - obj_state->os_temporary_not_available_timestamp_msec) % 1000));
-    }
+        nm_strbuf_append(&buf,
+                         &buf_size,
+                         ", %s-since=%" G_GINT64_FORMAT ".%03d",
+                         (obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL) ? "failed"
+                                                                                  : "failed-wait",
+                         (obj_state->os_failedobj_expiry_msec - now_msec) / 1000,
+                         (int) ((obj_state->os_failedobj_expiry_msec - now_msec) % 1000));
+    } else
+        nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
 
     return buf0;
 }
@@ -934,6 +957,7 @@ _obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboole
 
     if (!in_platform && !c_list_is_empty(&obj_state->os_zombie_lst)) {
         /* this is a zombie. We can forget about it.*/
+        nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
         nm_clear_nmp_object(&obj_state->os_plobj);
         c_list_unlink(&obj_state->os_zombie_lst);
         _LOGD("obj-state: zombie gone (untrack): %s",
@@ -949,8 +973,23 @@ _obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboole
     if (in_platform) {
         nmp_object_ref_set(&obj_state->os_plobj, obj);
         obj_state->os_was_in_platform = TRUE;
-        _LOGD("obj-state: appeared in platform: %s",
-              _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+        if (obj_state->os_failedobj_expiry_msec != 0) {
+            obj_state->os_failedobj_expiry_msec = 0;
+            if (obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL) {
+                _LOGT("obj-state: failed-obj: object now configured after failed earlier: %s",
+                      _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+            } else {
+                nm_prioq_remove(&self->priv.p->failedobj_prioq,
+                                obj_state,
+                                &obj_state->os_failedobj_prioq_idx);
+                _LOGT("obj-state: failed-obj: object now configured after waiting: %s",
+                      _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+            }
+        } else {
+            _LOGD("obj-state: appeared in platform: %s",
+                  _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+        }
+        nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
         goto out;
     }
 
@@ -1039,6 +1078,7 @@ _obj_states_update_all(NML3Cfg *self)
                 continue;
 
             if (obj_state->os_plobj && obj_state->os_nm_configured) {
+                nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
                 c_list_link_tail(&self->priv.p->obj_state_zombie_lst_head,
                                  &obj_state->os_zombie_lst);
                 obj_state->os_zombie_count = ZOMBIE_COUNT_START;
@@ -1049,6 +1089,9 @@ _obj_states_update_all(NML3Cfg *self)
 
             _LOGD("obj-state: untrack: %s",
                   _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+            nm_prioq_remove(&self->priv.p->failedobj_prioq,
+                            obj_state,
+                            &obj_state->os_failedobj_prioq_idx);
             g_hash_table_iter_remove(&h_iter);
         }
     }
@@ -1086,16 +1129,32 @@ _obj_states_sync_filter(NML3Cfg *self, const NMPObject *obj, NML3CfgCommitType c
         return TRUE;
     }
 
-    if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
-        /* we currently try to configure this address (but failed earlier).
-         * Definitely retry. */
-        return TRUE;
-    }
-
-    if (!obj_state->os_plobj && commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
-        && !nmp_object_get_force_commit(obj))
-        return FALSE;
-
+    /* One goal would be that we don't forcefully re-add routes which were
+     * externally removed (e.g. by the user via `ip route del`).
+     *
+     * However,
+     *
+     *  - some routes get automatically deleted by kernel (for example,
+     *  when we have an IPv4 route with RTA_PREFSRC set and the referenced
+     *  IPv4 address gets removed). The absence of such a route does not
+     *  mean that the user doesn't want the route there. It means, kernel
+     *  removed it because of some consistency check, but we want it back.
+     *  - a route with a non-zero gateway requires that the gateway is
+     *  directly reachable via an onlink route. The rules for this are
+     *  complex, but kernel will reject adding a route which has such a
+     *  gateway. If the user manually removed the needed onlink route, the
+     *  gateway route cannot be added in kernel ("Nexthop has invalid
+     *  gateway"). To handle that is a nightmare, so we always ensure that
+     *  the onlink route is there.
+     *  - a route with RTA_PREFSRC requires that such an address is
+     *  configured otherwise kernel rejects adding the route with "Invalid
+     *  prefsrc address"/"Invalid source address".  Removing an address can
+     *  thus prevent adding the route, which is a problem for us.
+     *
+     * So the goal is not tenable and causes problems. NetworkManager will
+     * try hard to re-add routes and address that it thinks should be
+     * present. If you externally remove them, then you are starting a
+     * fight where NetworkManager tries to re-add them on every commit. */
     return TRUE;
 }
 
@@ -1129,6 +1188,7 @@ static void
 _commit_collect_routes(NML3Cfg          *self,
                        int               addr_family,
                        NML3CfgCommitType commit_type,
+                       gboolean          any_addrs,
                        GPtrArray       **routes,
                        GPtrArray       **routes_nodev)
 {
@@ -1154,6 +1214,24 @@ _commit_collect_routes(NML3Cfg          *self,
         else {
             nm_assert(NMP_OBJECT_CAST_IP_ROUTE(obj)->ifindex == self->priv.ifindex);
 
+            if (!any_addrs) {
+                /* This is a unicast route (or a similar route, which has an
+                 * ifindex).
+                 *
+                 * However, during this commit we don't plan to configure any
+                 * IP addresses.  With `ipvx.method=manual` that should not be
+                 * possible. More likely, this is because the profile has
+                 * `ipvx.method=auto` and static routes.
+                 *
+                 * Don't configure any such routes before we also have at least
+                 * one IP address.
+                 *
+                 * This code applies to IPv4 and IPv6, however for IPv6 we
+                 * early on configure a link local address, so in practice the
+                 * branch is not taken for IPv6. */
+                continue;
+            }
+
             if (IS_IPv4 && NMP_OBJECT_CAST_IP4_ROUTE(obj)->weight > 0) {
                 /* This route needs to be registered as ECMP route. */
                 nm_netns_ip_route_ecmp_register(self->priv.netns, self, obj);
@@ -1246,6 +1324,7 @@ _obj_state_zombie_lst_get_prune_lists(NML3Cfg    *self,
         if (--obj_state->os_zombie_count == 0) {
             _LOGD("obj-state: prune zombie (untrack): %s",
                   _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+            nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
             g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
             continue;
         }
@@ -1280,6 +1359,7 @@ _obj_state_zombie_lst_prune_all(NML3Cfg *self, int addr_family)
         if (--obj_state->os_zombie_count == 0) {
             _LOGD("obj-state: zombie pruned during reapply (untrack): %s",
                   _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+            nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL);
             g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
             continue;
         }
@@ -3017,16 +3097,14 @@ nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr)
 /*****************************************************************************/
 
 gboolean
-nm_l3cfg_has_temp_not_available_obj(NML3Cfg *self, int addr_family)
+nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family)
 {
     ObjStateData *obj_state;
 
     nm_assert(NM_IS_L3CFG(self));
     nm_assert_addr_family(addr_family);
 
-    c_list_for_each_entry (obj_state,
-                           &self->priv.p->obj_state_temporary_not_available_lst_head,
-                           os_temporary_not_available_lst) {
+    nm_prioq_for_each (&self->priv.p->failedobj_prioq, obj_state) {
         if (NMP_OBJECT_GET_ADDR_FAMILY(obj_state->obj) == addr_family)
             return TRUE;
     }
@@ -3413,8 +3491,7 @@ nm_l3cfg_add_config(NML3Cfg              *self,
             .acd_timeout_msec_confdata = acd_timeout_msec,
             .priority_confdata         = priority,
             .pseudo_timestamp_confdata = ++self->priv.p->pseudo_timestamp_counter,
-            .force_commit_once = NM_FLAGS_HAS(config_flags, NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE),
-            .dirty_confdata    = FALSE,
+            .dirty_confdata            = FALSE,
         };
         changed = TRUE;
     } else {
@@ -3611,7 +3688,6 @@ typedef struct {
     NML3Cfg      *self;
     gconstpointer tag;
     bool          to_commit;
-    bool          force_commit_once;
 } L3ConfigMergeHookAddObjData;
 
 static gboolean
@@ -3629,9 +3705,6 @@ _l3_hook_add_obj_cb(const NML3ConfigData      *l3cd,
     nm_assert(obj);
     nm_assert(hook_result);
     nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
-    nm_assert(hook_result->force_commit == NM_OPTION_BOOL_DEFAULT);
-
-    hook_result->force_commit = hook_data->force_commit_once;
 
     switch (NMP_OBJECT_GET_TYPE(obj)) {
     case NMP_OBJECT_TYPE_IP4_ADDRESS:
@@ -3787,8 +3860,7 @@ _l3cfg_update_combined_config(NML3Cfg               *self,
             if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD))
                 continue;
 
-            hook_data.tag               = l3cd_data->tag_confdata;
-            hook_data.force_commit_once = l3cd_data->force_commit_once;
+            hook_data.tag = l3cd_data->tag_confdata;
 
             nm_l3_config_data_merge(l3cd,
                                     l3cd_data->l3cd,
@@ -3846,7 +3918,6 @@ _l3cfg_update_combined_config(NML3Cfg               *self,
                     IS_IPv4 ? AF_INET : AF_INET6,
                     l3cd_data->default_route_table_x[IS_IPv4],
                     l3cd_data->default_route_metric_x[IS_IPv4],
-                    l3cd_data->force_commit_once,
                     l3cd_data->l3cd);
             }
         }
@@ -3921,79 +3992,91 @@ out:
 /*****************************************************************************/
 
 static gboolean
-_routes_temporary_not_available_timeout(gpointer user_data)
+_failedobj_timeout_cb(gpointer user_data)
 {
-    NML3Cfg      *self = NM_L3CFG(user_data);
-    ObjStateData *obj_state;
-    gint64        now_msec;
-    gint64        expiry_msec;
+    NML3Cfg *self = NM_L3CFG(user_data);
 
-    nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
+    _LOGT("obj-state: failed-obj: handle timeout");
 
-    obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head,
-                                   ObjStateData,
-                                   os_temporary_not_available_lst);
+    nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source);
 
-    if (!obj_state)
-        return G_SOURCE_CONTINUE;
+    nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO);
 
-    now_msec = nm_utils_get_monotonic_timestamp_msec();
+    return G_SOURCE_CONTINUE;
+}
 
-    expiry_msec = obj_state->os_temporary_not_available_timestamp_msec
-                  + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC;
+static void
+_failedobj_reschedule(NML3Cfg *self, gint64 now_msec)
+{
+    char          sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    ObjStateData *obj_state;
 
-    if (now_msec < expiry_msec) {
-        /* the timeout is not yet reached. Restart the timer... */
-        self->priv.p->obj_state_temporary_not_available_timeout_source =
-            nm_g_timeout_add_source(expiry_msec - now_msec,
-                                    _routes_temporary_not_available_timeout,
-                                    self);
-        return G_SOURCE_CONTINUE;
+    nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+
+again:
+    obj_state = nm_prioq_peek(&self->priv.p->failedobj_prioq);
+
+    if (obj_state && obj_state->os_failedobj_expiry_msec <= now_msec) {
+        /* The object is already expired... */
+
+        /* we shouldn't have a "os_plobj", because if we had, we should have
+         * removed "obj_state" from the queue. */
+        nm_assert(!obj_state->os_plobj);
+
+        /* we need to have an "obj", otherwise the "obj_state" instance
+         * shouldn't exist (as it also has not "os_plobj"). */
+        nm_assert(obj_state->obj);
+
+        /* It seems that nm_platform_ip_route_sync() signaled success and did
+         * not report the route as missing. Regardless, it is still not
+         * configured and the timeout expired. */
+        nm_prioq_remove(&self->priv.p->failedobj_prioq,
+                        obj_state,
+                        &obj_state->os_failedobj_prioq_idx);
+        _LOGW(
+            "missing IPv%c route: %s",
+            nm_utils_addr_family_to_char(NMP_OBJECT_GET_TYPE(obj_state->obj)),
+            nmp_object_to_string(obj_state->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+        goto again;
     }
 
-    /* One (or several) routes expired. We emit a signal, but we don't schedule it again.
-     * We expect the callers to commit again, which will one last time try to configure
-     * the route. If that again fails, we detect the timeout, log a warning and don't
-     * track the object as not temporary-not-available anymore. */
-    _nm_l3cfg_emit_signal_notify_simple(
-        self,
-        NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED);
-    return G_SOURCE_CONTINUE;
+    if (!obj_state) {
+        if (nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source))
+            _LOGT("obj-state: failed-obj: cancel timeout");
+        return;
+    }
+
+    if (nm_g_timeout_reschedule(&self->priv.p->failedobj_timeout_source,
+                                &self->priv.p->failedobj_timeout_expiry_msec,
+                                obj_state->os_failedobj_expiry_msec,
+                                _failedobj_timeout_cb,
+                                self)) {
+        _LOGT(
+            "obj-state: failed-obj: schedule timeout in %" G_GINT64_FORMAT " msec",
+            NM_MAX((gint64) 0,
+                   obj_state->os_failedobj_expiry_msec - nm_utils_get_monotonic_timestamp_msec()));
+    }
 }
 
-static gboolean
-_routes_temporary_not_available_update(NML3Cfg   *self,
-                                       int        addr_family,
-                                       GPtrArray *routes_temporary_not_available_arr)
+static void
+_failedobj_handle_routes(NML3Cfg *self, int addr_family, GPtrArray *routes_failed)
 {
-    ObjStateData   *obj_state;
-    ObjStateData   *obj_state_safe;
-    gint64          now_msec;
-    gboolean        prune_all = FALSE;
-    gboolean        success   = TRUE;
-    guint           i;
-    const NMPClass *klass;
-
-    klass    = nmp_class_from_type(NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)));
-    now_msec = nm_utils_get_monotonic_timestamp_msec();
-
-    if (nm_g_ptr_array_len(routes_temporary_not_available_arr) <= 0) {
-        prune_all = TRUE;
-        goto out_prune;
-    }
+    const gint64  now_msec = nm_utils_get_monotonic_timestamp_msec();
+    char          sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    ObjStateData *obj_state;
+    guint         i;
 
-    c_list_for_each_entry (obj_state,
-                           &self->priv.p->obj_state_temporary_not_available_lst_head,
-                           os_temporary_not_available_lst) {
-        if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) {
-            nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0);
-            obj_state->os_tna_dirty = TRUE;
-        }
-    }
+    if (!routes_failed)
+        return;
 
-    for (i = 0; i < routes_temporary_not_available_arr->len; i++) {
-        const NMPObject *o = routes_temporary_not_available_arr->pdata[i];
-        char             sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
+    for (i = 0; i < routes_failed->len; i++) {
+        const NMPObject          *o                    = routes_failed->pdata[i];
+        const NMPlatformIPXRoute *rt                   = NMP_OBJECT_CAST_IPX_ROUTE(o);
+        gboolean                  just_started_to_fail = FALSE;
+        gboolean                  just_failed          = FALSE;
+        gboolean                  arm_timer            = FALSE;
+        int                       grace_timeout_msec;
+        gint64                    grace_expiry_mesc;
 
         nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)));
 
@@ -4005,70 +4088,83 @@ _routes_temporary_not_available_update(NML3Cfg   *self,
             continue;
         }
 
-        if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
-            nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0
-                      && obj_state->os_temporary_not_available_timestamp_msec <= now_msec);
-
-            if (!obj_state->os_tna_dirty) {
-                /* Odd, this only can happen if routes_temporary_not_available_arr contains duplicates.
-                 * It should not. */
-                nm_assert_not_reached();
-                continue;
-            }
-
-            if (now_msec > obj_state->os_temporary_not_available_timestamp_msec
-                               + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) {
-                /* Timeout. Could not add this address.
-                 *
-                 * For now, keep it obj_state->os_tna_dirty and prune it below. */
-                _LOGW("failure to add IPv%c route: %s",
-                      nm_utils_addr_family_to_char(addr_family),
-                      nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
-                success = FALSE;
-                continue;
-            }
-
-            obj_state->os_tna_dirty = FALSE;
+        if (obj_state->os_plobj) {
+            /* This object is apparently present in platform. Not sure what this failure report
+             * is about. Probably some harmless glitch. Ignore. */
             continue;
         }
 
-        _LOGT("(temporarily) unable to add IPv%c route: %s",
-              nm_utils_addr_family_to_char(addr_family),
-              nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+        /* This route failed, but why? That determines the grace time that we
+         * give before considering it bad. */
+        if (!nm_ip_addr_is_null(addr_family,
+                                nm_platform_ip_route_get_pref_src(addr_family, &rt->rx))) {
+            /* This route has a pref_src. A common cause for being unable to
+             * configure such routes, is that the referenced IP address is not
+             * configured/ready (yet). Give a longer timeout to this case. */
+            grace_timeout_msec = 10000;
+        } else {
+            /* Other route don't have any grace time. There is no retry/wait,
+             * they are a failure right away. */
+            grace_timeout_msec = 0;
+        }
 
-        obj_state->os_tna_dirty                              = FALSE;
-        obj_state->os_temporary_not_available_timestamp_msec = now_msec;
-        c_list_link_tail(&self->priv.p->obj_state_temporary_not_available_lst_head,
-                         &obj_state->os_temporary_not_available_lst);
-    }
+        grace_expiry_mesc = now_msec + grace_timeout_msec;
 
-out_prune:
-    c_list_for_each_entry_safe (obj_state,
-                                obj_state_safe,
-                                &self->priv.p->obj_state_temporary_not_available_lst_head,
-                                os_temporary_not_available_lst) {
-        if (prune_all || obj_state->os_tna_dirty) {
-            if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) {
-                obj_state->os_temporary_not_available_timestamp_msec = 0;
-                c_list_unlink(&obj_state->os_temporary_not_available_lst);
+        if (obj_state->os_failedobj_expiry_msec == 0) {
+            /* This is a new failure that we didn't see before... */
+            obj_state->os_failedobj_expiry_msec = grace_expiry_mesc;
+            if (grace_timeout_msec == 0)
+                just_failed = TRUE;
+            else {
+                arm_timer            = TRUE;
+                just_started_to_fail = TRUE;
             }
+        } else {
+            if (obj_state->os_failedobj_expiry_msec > grace_expiry_mesc) {
+                /* Shorten the grace timeout. We anyway rearm below... */
+                obj_state->os_failedobj_expiry_msec = grace_expiry_mesc;
+            }
+            if (obj_state->os_failedobj_expiry_msec <= now_msec) {
+                /* The grace period is (already) expired. */
+                if (obj_state->os_failedobj_prioq_idx != NM_PRIOQ_IDX_NULL) {
+                    /* We are still tracking the element. It just is about to become failed. */
+                    just_failed = TRUE;
+                }
+            } else
+                arm_timer = TRUE;
         }
-    }
 
-    nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
-
-    obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head,
-                                   ObjStateData,
-                                   os_temporary_not_available_lst);
-    if (obj_state) {
-        self->priv.p->obj_state_temporary_not_available_timeout_source =
-            nm_g_timeout_add_source((obj_state->os_temporary_not_available_timestamp_msec
-                                     + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec),
-                                    _routes_temporary_not_available_timeout,
-                                    self);
+        nm_prioq_update(&self->priv.p->failedobj_prioq,
+                        obj_state,
+                        &obj_state->os_failedobj_prioq_idx,
+                        arm_timer);
+
+        if (just_failed) {
+            _LOGW("unable to configure IPv%c route: %s",
+                  nm_utils_addr_family_to_char(addr_family),
+                  nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+        } else if (just_started_to_fail) {
+            _LOGT("obj-state: failed-obj: unable to configure %s. Wait for %d msec",
+                  _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)),
+                  grace_timeout_msec);
+        }
     }
+}
+
+static int
+_failedobj_prioq_cmp(gconstpointer a, gconstpointer b)
+{
+    const ObjStateData *object_state_a = a;
+    const ObjStateData *object_state_b = b;
+
+    nm_assert(object_state_a);
+    nm_assert(object_state_a->os_failedobj_expiry_msec > 0);
+    nm_assert(object_state_b);
+    nm_assert(object_state_b->os_failedobj_expiry_msec > 0);
 
-    return success;
+    NM_CMP_SELF(object_state_a, object_state_b);
+    NM_CMP_FIELD(object_state_a, object_state_b, os_failedobj_expiry_msec);
+    return 0;
 }
 
 /*****************************************************************************/
@@ -4391,6 +4487,147 @@ _rp_filter_update(NML3Cfg *self, gboolean reapply)
 
 /*****************************************************************************/
 
+static void
+_routes_watch_ip_addrs_cb(NMNetns                       *netns,
+                          NMNetnsWatcherType             watcher_type,
+                          const NMNetnsWatcherData      *watcher_data,
+                          gconstpointer                  tag,
+                          const NMNetnsWatcherEventData *event_data,
+                          gpointer                       user_data)
+{
+    const int IS_IPv4 = NM_IS_IPv4(watcher_data->ip_addr.addr.addr_family);
+    NML3Cfg  *self    = user_data;
+    char      sbuf[NM_INET_ADDRSTRLEN];
+
+    if (NMP_OBJECT_CAST_IP_ADDRESS(event_data->ip_addr.obj)->ifindex == self->priv.ifindex) {
+        if (self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4] > 0) {
+            /* We are currently commiting IP addresses on this very interface.
+             * We can ignore the event. Also, because we will sync the routes
+             * immediately after already.  So even if somebody externally added
+             * the address just this very moment, we would still do the commit
+             * at the right time to ensure our routes are there. */
+            return;
+        }
+    }
+
+    if (event_data->ip_addr.change_type == NM_PLATFORM_SIGNAL_REMOVED)
+        return;
+
+    _LOGT("watched ip-address %s changed. Schedule an idle commit",
+          nm_inet_ntop(watcher_data->ip_addr.addr.addr_family,
+                       &watcher_data->ip_addr.addr.addr,
+                       sbuf));
+    nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO);
+}
+
+static void
+_routes_watch_ip_addrs(NML3Cfg *self, int addr_family, GPtrArray *addresses, GPtrArray *routes)
+{
+    gconstpointer const TAG          = _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family);
+    NMNetnsWatcherData  watcher_data = {
+         .ip_addr =
+            {
+                 .addr =
+                    {
+                         .addr_family = addr_family,
+                    },
+            },
+    };
+    guint i;
+    guint j;
+
+    /* IP routes that have a pref_src, can only be configured in kernel if that
+     * address exists (and is non-tentative, in case of IPv6).  That address
+     * might be on another interface. So we actually watch all other
+     * interfaces.
+     *
+     * Note that while we track failure to configure routes via "failedobj"
+     * mechanism, we eagerly register watchers, even if the route is already
+     * successfully configured or if the route is to be configure the first
+     * time.  Maybe that could be improved, but
+     * - watchers should be cheap unless they notify the event.
+     * - upon change we do an async commit, which is maybe not entirely cheap
+     *   but cheap enough. More importantly, committing is something that
+     *   *always* should be permissible -- because NML3Cfg has multiple,
+     *   independent users, that don't know about each other and which
+     *   independently are allowed to issue a commit when they think something
+     *   relevant changed. If there are really too many, unnecessary commits,
+     *   then the cause needs to be understood and addressed explicitly. */
+
+    if (!routes)
+        goto out;
+
+    for (i = 0; i < routes->len; i++) {
+        const NMPlatformIPRoute *rt = NMP_OBJECT_CAST_IP_ROUTE(routes->pdata[i]);
+        gconstpointer            pref_src;
+
+        nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(routes->pdata[i]) == addr_family);
+
+        pref_src = nm_platform_ip_route_get_pref_src(addr_family, rt);
+
+        if (nm_ip_addr_is_null(addr_family, pref_src))
+            continue;
+
+        if (NM_IS_IPv4(addr_family)) {
+            if (addresses) {
+                /* This nested loop makes the whole operation O(n*m). We still
+                 * do it that way, because it's probably faster to just iterate
+                 * over the few addresses instead of building a lookup index to
+                 * get it in O(n+m). */
+                for (j = 0; j < addresses->len; j++) {
+                    const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(addresses->pdata[j]);
+
+                    nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(addresses->pdata[j]) == addr_family);
+
+                    if (nm_ip_addr_equal(addr_family, pref_src, a->address_ptr)) {
+                        /* We optimize for the case where the required address
+                         * is about be configured in the same commit. That is a
+                         * common case, because our DHCP routes have prefsrc
+                         * set, and we commonly have the respective IP address
+                         * ready.  Otherwise, the very common DHCP case would
+                         * also require the overhead of registering a watcher
+                         * (every time).
+                         */
+                        goto next;
+                    }
+                }
+            }
+        } else {
+            /* For IPv6, the prefsrc address must also be non-tentative (or
+             * IFA_F_OPTIMISTIC).  So by only looking at the addresses we are
+             * about to configure, it's not clear whether we will be able to
+             * configure the route too.
+             *
+             * Maybe we could check current platform, whether the address
+             * exists there as non-tentative, but that seems fragile.
+             *
+             * Maybe we should only register watchers, after we encountered a
+             * failure to configure a route, but that seems complicated (and
+             * has the potential to be wrong).
+             *
+             * The overhead for always watching the IPv6 address should be
+             * acceptably small. So just do that.
+             */
+        }
+
+        nm_assert(watcher_data.ip_addr.addr.addr_family == addr_family);
+        nm_ip_addr_set(addr_family, &watcher_data.ip_addr.addr.addr, pref_src);
+
+        nm_netns_watcher_add(self->priv.netns,
+                             NM_NETNS_WATCHER_TYPE_IP_ADDR,
+                             &watcher_data,
+                             TAG,
+                             _routes_watch_ip_addrs_cb,
+                             self);
+next:
+        (void) 0;
+    }
+
+out:
+    nm_netns_watcher_remove_all(self->priv.netns, TAG, FALSE);
+}
+/*****************************************************************************/
+
 static gboolean
 _global_tracker_mptcp_untrack(NML3Cfg *self, int addr_family)
 {
@@ -4576,24 +4813,22 @@ _l3_commit_mptcp(NML3Cfg *self, NML3CfgCommitType commit_type)
     _rp_filter_update(self, reapply);
 }
 
-static gboolean
+static void
 _l3_commit_one(NML3Cfg              *self,
                int                   addr_family,
                NML3CfgCommitType     commit_type,
                gboolean              changed_combined_l3cd,
                const NML3ConfigData *l3cd_old)
 {
-    const int                    IS_IPv4                            = NM_IS_IPv4(addr_family);
-    gs_unref_ptrarray GPtrArray *addresses                          = NULL;
-    gs_unref_ptrarray GPtrArray *routes                             = NULL;
-    gs_unref_ptrarray GPtrArray *routes_nodev                       = NULL;
-    gs_unref_ptrarray GPtrArray *addresses_prune                    = NULL;
-    gs_unref_ptrarray GPtrArray *routes_prune                       = NULL;
-    gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL;
+    const int                    IS_IPv4         = NM_IS_IPv4(addr_family);
+    gs_unref_ptrarray GPtrArray *addresses       = NULL;
+    gs_unref_ptrarray GPtrArray *routes          = NULL;
+    gs_unref_ptrarray GPtrArray *routes_nodev    = NULL;
+    gs_unref_ptrarray GPtrArray *addresses_prune = NULL;
+    gs_unref_ptrarray GPtrArray *routes_prune    = NULL;
+    gs_unref_ptrarray GPtrArray *routes_failed   = NULL;
     NMIPRouteTableSyncMode       route_table_sync;
-    gboolean                     final_failure_for_temporary_not_available = FALSE;
     char                         sbuf_commit_type[50];
-    gboolean                     success = TRUE;
     guint                        i;
 
     nm_assert(NM_IS_L3CFG(self));
@@ -4609,7 +4844,12 @@ _l3_commit_one(NML3Cfg              *self,
 
     addresses = _commit_collect_addresses(self, addr_family, commit_type);
 
-    _commit_collect_routes(self, addr_family, commit_type, &routes, &routes_nodev);
+    _commit_collect_routes(self,
+                           addr_family,
+                           commit_type,
+                           nm_g_ptr_array_len(addresses) > 0,
+                           &routes,
+                           &routes_nodev);
 
     route_table_sync =
         self->priv.p->combined_l3cd_commited
@@ -4689,9 +4929,14 @@ _l3_commit_one(NML3Cfg              *self,
             }
         }
     }
+
+    _routes_watch_ip_addrs(self, addr_family, addresses, routes);
+
     /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */
     /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_mtu(). */
 
+    self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]++;
+
     nm_platform_ip_address_sync(self->priv.platform,
                                 addr_family,
                                 self->priv.ifindex,
@@ -4701,26 +4946,18 @@ _l3_commit_one(NML3Cfg              *self,
                                     ? NMP_IP_ADDRESS_SYNC_FLAGS_NONE
                                     : NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE);
 
-    _nodev_routes_sync(self, addr_family, commit_type, routes_nodev);
-
-    if (!nm_platform_ip_route_sync(self->priv.platform,
-                                   addr_family,
-                                   self->priv.ifindex,
-                                   routes,
-                                   routes_prune,
-                                   &routes_temporary_not_available_arr))
-        success = FALSE;
+    self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]--;
 
-    final_failure_for_temporary_not_available = FALSE;
-    if (!_routes_temporary_not_available_update(self,
-                                                addr_family,
-                                                routes_temporary_not_available_arr))
-        final_failure_for_temporary_not_available = TRUE;
+    _nodev_routes_sync(self, addr_family, commit_type, routes_nodev);
 
-    /* FIXME(l3cfg) */
-    (void) final_failure_for_temporary_not_available;
+    nm_platform_ip_route_sync(self->priv.platform,
+                              addr_family,
+                              self->priv.ifindex,
+                              routes,
+                              routes_prune,
+                              &routes_failed);
 
-    return success;
+    _failedobj_handle_routes(self, addr_family, routes_failed);
 }
 
 static void
@@ -4733,7 +4970,6 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
     gboolean                                 is_sticky_update      = FALSE;
     char                                     sbuf_ct[30];
     gboolean                                 changed_combined_l3cd;
-    guint                                    i;
 
     g_return_if_fail(NM_IS_L3CFG(self));
     nm_assert(NM_IN_SET(commit_type,
@@ -4794,19 +5030,12 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
     _l3_commit_one(self, AF_INET, commit_type, changed_combined_l3cd, l3cd_old);
     _l3_commit_one(self, AF_INET6, commit_type, changed_combined_l3cd, l3cd_old);
 
+    _failedobj_reschedule(self, 0);
+
     _l3_commit_mptcp(self, commit_type);
 
     _l3_acd_data_process_changes(self);
 
-    if (self->priv.p->l3_config_datas) {
-        for (i = 0; i < self->priv.p->l3_config_datas->len; i++) {
-            L3ConfigData *l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, i);
-
-            if (l3_config_data->force_commit_once)
-                l3_config_data->force_commit_once = FALSE;
-        }
-    }
-
     nm_assert(self->priv.p->commit_reentrant_count == 1);
     self->priv.p->commit_reentrant_count--;
 
@@ -5164,7 +5393,6 @@ nm_l3cfg_init(NML3Cfg *self)
     c_list_init(&self->priv.p->acd_event_notify_lst_head);
     c_list_init(&self->priv.p->commit_type_lst_head);
     c_list_init(&self->priv.p->obj_state_lst_head);
-    c_list_init(&self->priv.p->obj_state_temporary_not_available_lst_head);
     c_list_init(&self->priv.p->obj_state_zombie_lst_head);
     c_list_init(&self->priv.p->blocked_lst_head_4);
     c_list_init(&self->priv.p->blocked_lst_head_6);
@@ -5176,6 +5404,8 @@ nm_l3cfg_init(NML3Cfg *self)
                                                          nmp_object_indirect_id_equal,
                                                          _obj_state_data_free,
                                                          NULL);
+
+    nm_prioq_init(&self->priv.p->failedobj_prioq, _failedobj_prioq_cmp);
 }
 
 static void
@@ -5214,6 +5444,18 @@ finalize(GObject *object)
     NML3Cfg *self = NM_L3CFG(object);
     gboolean changed;
 
+    if (self->priv.netns) {
+        nm_netns_watcher_remove_all(self->priv.netns,
+                                    _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET),
+                                    TRUE);
+        nm_netns_watcher_remove_all(self->priv.netns,
+                                    _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET6),
+                                    TRUE);
+    }
+
+    nm_prioq_destroy(&self->priv.p->failedobj_prioq);
+    nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source);
+
     nm_assert(c_list_is_empty(&self->internal_netns.signal_pending_lst));
     nm_assert(c_list_is_empty(&self->internal_netns.ecmp_track_ifindex_lst_head));
 
@@ -5241,11 +5483,8 @@ finalize(GObject *object)
     nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry);
     nm_clear_g_source_inst(&self->priv.p->nacd_event_down_source);
 
-    nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
-
     nm_clear_pointer(&self->priv.p->obj_state_hash, g_hash_table_destroy);
     nm_assert(c_list_is_empty(&self->priv.p->obj_state_lst_head));
-    nm_assert(c_list_is_empty(&self->priv.p->obj_state_temporary_not_available_lst_head));
     nm_assert(c_list_is_empty(&self->priv.p->obj_state_zombie_lst_head));
 
     if (_nodev_routes_untrack(self, AF_INET))
diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h
index 9d622b4a..5ee201e7 100644
--- a/src/core/nm-l3cfg.h
+++ b/src/core/nm-l3cfg.h
@@ -55,15 +55,11 @@ typedef enum _nm_packed {
  *   "don't change" behavior. At least once. If the address/route
  *   is still not (no longer) configured on the subsequent
  *   commit, it's not getting added again.
- * @NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE: if set, objects in the
- *   NML3ConfigData are committed to platform even if they were
- *   removed externally.
  */
 typedef enum _nm_packed {
     NM_L3CFG_CONFIG_FLAGS_NONE               = 0,
     NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD       = (1LL << 0),
     NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE = (1LL << 1),
-    NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE         = (1LL << 2),
 } NML3CfgConfigFlags;
 
 typedef enum _nm_packed {
@@ -132,8 +128,6 @@ typedef enum {
      * and neither should you call into NML3Cfg again (reentrancy). */
     NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED,
 
-    NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED,
-
     NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT,
 
     /* emitted before the merged l3cd is committed to platform.
@@ -412,7 +406,7 @@ gboolean nm_l3cfg_check_ready(NML3Cfg               *self,
                               NML3CfgCheckReadyFlags flags,
                               GArray               **conflicts);
 
-gboolean nm_l3cfg_has_temp_not_available_obj(NML3Cfg *self, int addr_family);
+gboolean nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family);
 
 /*****************************************************************************/
 
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index 19ca1d1e..9c721220 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -69,6 +69,36 @@ typedef struct {
     bool os_owner : 1;
 } RfkillRadioState;
 
+#define AUTOCONNECT_RESET_RETRIES_TIMER_SEC 300
+
+typedef struct {
+    NMDevice             *device;
+    NMSettingsConnection *sett_conn;
+    CList                 dev_lst;
+    CList                 con_lst;
+
+    /* Autoconnet retries needs to be tracked for each (device, connection)
+     * tuple because when a connection is a multiconnect one, each valid device
+     * must try to autoconnect the retries defined in the connection. */
+    struct {
+        guint32                            retries;
+        gint32                             blocked_until_sec;
+        NMSettingsAutoconnectBlockedReason blocked_reason;
+        bool                               initialized : 1;
+    } autoconnect;
+
+} DevConData;
+
+#define DEV_CON_DATA_LOG_FMT \
+    "device[" NM_HASH_OBFUSCATE_PTR_FMT ",%s]-profile[" NM_HASH_OBFUSCATE_PTR_FMT ",%s]"
+
+/* This is an unsafe macro (it evaluates the macro arguments multiple times and is non-function-like). */
+#define DEV_CON_DATA_LOG_ARGS(device, sett_conn)                                                  \
+    NM_HASH_OBFUSCATE_PTR(device), nm_device_get_iface(device), NM_HASH_OBFUSCATE_PTR(sett_conn), \
+        nm_settings_connection_get_id(sett_conn)
+
+#define DEV_CON_DATA_LOG_ARGS_DATA(data) DEV_CON_DATA_LOG_ARGS((data)->device, (data)->sett_conn)
+
 typedef enum {
     ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL,
     ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER,
@@ -173,6 +203,8 @@ typedef struct {
     } prop_filter;
     NMRfkillManager *rfkill_mgr;
 
+    GHashTable *devcon_data_dict;
+
     CList link_cb_lst;
 
     NMCheckpointManager *checkpoint_mgr;
@@ -415,6 +447,12 @@ static void _activation_auth_done(NMManager             *self,
 
 static void _rfkill_update(NMManager *self, NMRfkillType rtype);
 
+static DevConData *_devcon_lookup_data(NMManager            *self,
+                                       NMDevice             *device,
+                                       NMSettingsConnection *sett_conn,
+                                       gboolean              create,
+                                       gboolean              log_creation);
+
 /*****************************************************************************/
 
 static NM_CACHED_QUARK_FCN("autoconnect-root", autoconnect_root_quark);
@@ -1212,6 +1250,473 @@ active_connection_get_by_path(NMManager *self, const char *path)
 
 /*****************************************************************************/
 
+static guint32
+_autoconnect_retries_initial(NMSettingsConnection *sett_conn)
+{
+    NMSettingConnection *s_con;
+    int                  retries = -1;
+
+    s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn));
+    if (s_con)
+        retries = nm_setting_connection_get_autoconnect_retries(s_con);
+
+    if (retries == -1)
+        retries = nm_config_data_get_autoconnect_retries_default(NM_CONFIG_GET_DATA);
+
+    nm_assert(retries >= 0 && retries <= G_MAXINT32);
+
+    if (retries == 0)
+        return NM_AUTOCONNECT_RETRIES_FOREVER;
+    return (guint32) retries;
+}
+
+static gboolean
+_autoconnect_retries_set(NMManager *self, DevConData *data, guint32 retries, gboolean is_reset)
+{
+    gboolean changed = FALSE;
+    gint32   blocked_until_sec;
+
+    nm_assert(data);
+
+    if (!data->autoconnect.initialized || data->autoconnect.retries != retries) {
+        data->autoconnect.initialized = TRUE;
+        data->autoconnect.retries     = retries;
+        changed                       = TRUE;
+    }
+
+    if (retries != 0) {
+        blocked_until_sec = 0;
+    } else {
+        /* NOTE: the blocked time must be identical for all connections, otherwise
+         * the tracking of resetting the retry count in NMPolicy needs adjustment
+         * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
+         * the next-timeout every time a connection gets blocked). */
+        blocked_until_sec =
+            nm_utils_get_monotonic_timestamp_sec() + AUTOCONNECT_RESET_RETRIES_TIMER_SEC;
+    }
+
+    if (data->autoconnect.blocked_until_sec != blocked_until_sec) {
+        data->autoconnect.blocked_until_sec = blocked_until_sec;
+        changed                             = TRUE;
+    }
+
+    if (changed) {
+        char sbuf[200];
+
+        _LOGT(LOGD_SETTINGS,
+              "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": retries set %u%s%s",
+              DEV_CON_DATA_LOG_ARGS_DATA(data),
+              retries,
+              is_reset ? " (is-reset)" : "",
+              blocked_until_sec == 0 ? ""
+                                     : nm_sprintf_buf(sbuf,
+                                                      " (blocked for %d sec)",
+                                                      AUTOCONNECT_RESET_RETRIES_TIMER_SEC));
+    }
+
+    return changed;
+}
+
+/**
+ * nm_manager_devcon_autoconnect_retries_get:
+ * @self: the #NMManager
+ * @device: the #NMDevice
+ * @sett_conn: the #NMSettingsConnection
+ *
+ * Returns the number of autoconnect retries left for the (device, connection)
+ * tuple. If the value is not yet set, initialize it with the value from the
+ * connection or with the global default.
+ */
+guint32
+nm_manager_devcon_autoconnect_retries_get(NMManager            *self,
+                                          NMDevice             *device,
+                                          NMSettingsConnection *sett_conn)
+{
+    DevConData *data;
+
+    nm_assert(NM_IS_MANAGER(self));
+    nm_assert(NM_IS_DEVICE(device));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+    nm_assert(self == nm_device_get_manager(device));
+    nm_assert(self == nm_settings_connection_get_manager(sett_conn));
+
+    data = _devcon_lookup_data(self, device, sett_conn, TRUE, FALSE);
+
+    if (G_UNLIKELY(!data->autoconnect.initialized))
+        _autoconnect_retries_set(self, data, _autoconnect_retries_initial(sett_conn), FALSE);
+
+    return data->autoconnect.retries;
+}
+
+void
+nm_manager_devcon_autoconnect_retries_set(NMManager            *self,
+                                          NMDevice             *device,
+                                          NMSettingsConnection *sett_conn,
+                                          guint32               retries)
+{
+    _autoconnect_retries_set(self,
+                             _devcon_lookup_data(self, device, sett_conn, TRUE, FALSE),
+                             retries,
+                             FALSE);
+}
+
+gboolean
+nm_manager_devcon_autoconnect_retries_reset(NMManager            *self,
+                                            NMDevice             *device,
+                                            NMSettingsConnection *sett_conn)
+{
+    DevConData *data;
+    guint32     retries_initial;
+    gboolean    changed = FALSE;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+
+    retries_initial = _autoconnect_retries_initial(sett_conn);
+
+    if (device) {
+        return _autoconnect_retries_set(self,
+                                        _devcon_lookup_data(self, device, sett_conn, TRUE, FALSE),
+                                        retries_initial,
+                                        TRUE);
+    }
+
+    c_list_for_each_entry (data, &sett_conn->devcon_con_lst_head, con_lst) {
+        if (_autoconnect_retries_set(self, data, retries_initial, TRUE))
+            changed = TRUE;
+    }
+
+    return changed;
+}
+
+/**
+ * nm_manager_devcon_autoconnect_reset_reconnect_all:
+ * @self: the #NMManager
+ * @device: the #NMDevice
+ * @sett_conn: the #NMSettingsConnection
+ * @only_no_secrets: boolean to reset all reasons or only no secrets.
+ *
+ * Returns a boolean indicating if something changed or not when resetting the
+ * blocked reasons. If a #NMDevice is present then we also reset the reasons
+ * for the (device, connection) tuple.
+ */
+gboolean
+nm_manager_devcon_autoconnect_reset_reconnect_all(NMManager            *self,
+                                                  NMDevice             *device,
+                                                  NMSettingsConnection *sett_conn,
+                                                  gboolean              only_no_secrets)
+{
+    gboolean changed = FALSE;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+
+    if (only_no_secrets) {
+        /* we only reset the no-secrets blocked flag. */
+        if (nm_settings_connection_autoconnect_blocked_reason_set(
+                sett_conn,
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS,
+                FALSE)) {
+            /* maybe the connection is still blocked afterwards for other reasons
+             * and in the larger picture nothing changed. Check if the connection
+             * is still blocked or not. */
+            if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
+                changed = TRUE;
+        }
+
+        return changed;
+    }
+
+    /* we reset the tries-count and any blocked-reason... */
+
+    nm_manager_devcon_autoconnect_retries_reset(self, NULL, sett_conn);
+
+    if (device) {
+        if (nm_manager_devcon_autoconnect_blocked_reason_set(
+                self,
+                device,
+                sett_conn,
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
+                FALSE))
+            changed = TRUE;
+    }
+
+    /* we remove all the blocked reason from the connection, if something
+     * happened, then it means the status changed */
+    if (nm_settings_connection_autoconnect_blocked_reason_set(
+            sett_conn,
+            NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS
+                | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST,
+            FALSE))
+        changed = TRUE;
+
+    return changed;
+}
+
+gint32
+nm_manager_devcon_autoconnect_retries_blocked_until(NMManager            *self,
+                                                    NMDevice             *device,
+                                                    NMSettingsConnection *sett_conn)
+{
+    DevConData *data;
+    gint32      min_stamp;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+
+    if (device) {
+        data = _devcon_lookup_data(self, device, sett_conn, FALSE, FALSE);
+
+        if (!data)
+            return 0;
+
+        return data->autoconnect.blocked_until_sec;
+    }
+
+    min_stamp = 0;
+    c_list_for_each_entry (data, &sett_conn->devcon_con_lst_head, con_lst) {
+        gint32 condev_stamp = data->autoconnect.blocked_until_sec;
+
+        if (condev_stamp == 0)
+            continue;
+
+        if (min_stamp == 0 || min_stamp > condev_stamp)
+            min_stamp = condev_stamp;
+    }
+
+    return min_stamp;
+}
+
+gboolean
+nm_manager_devcon_autoconnect_is_blocked(NMManager            *self,
+                                         NMDevice             *device,
+                                         NMSettingsConnection *sett_conn)
+{
+    DevConData *data;
+
+    nm_assert(NM_IS_DEVICE(device));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+
+    if (nm_settings_connection_autoconnect_is_blocked(sett_conn))
+        return TRUE;
+
+    data = _devcon_lookup_data(self, device, sett_conn, FALSE, FALSE);
+
+    if (!data)
+        return FALSE;
+
+    if (data->autoconnect.blocked_reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE)
+        return TRUE;
+
+    if (data->autoconnect.initialized && data->autoconnect.retries == 0)
+        return TRUE;
+
+    return FALSE;
+}
+
+gboolean
+nm_manager_devcon_autoconnect_blocked_reason_set(NMManager                         *self,
+                                                 NMDevice                          *device,
+                                                 NMSettingsConnection              *sett_conn,
+                                                 NMSettingsAutoconnectBlockedReason value,
+                                                 gboolean                           set)
+{
+    NMSettingsAutoconnectBlockedReason v;
+    DevConData                        *data;
+    gboolean                           changed = FALSE;
+    char                               buf[100];
+
+    nm_assert(!sett_conn || NM_IS_SETTINGS_CONNECTION(sett_conn));
+    nm_assert(!device || NM_IS_DEVICE(device));
+    nm_assert(value != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE);
+    nm_assert(!NM_FLAGS_ANY(value, ~(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED)));
+
+    if (!sett_conn) {
+        if (!device)
+            g_return_val_if_reached(FALSE);
+        c_list_for_each_entry (data, &device->devcon_dev_lst_head, dev_lst) {
+            v = data->autoconnect.blocked_reason;
+            v = NM_FLAGS_ASSIGN(v, value, set);
+
+            if (data->autoconnect.blocked_reason == v)
+                continue;
+
+            _LOGT(LOGD_SETTINGS,
+                  "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": set blocked reason %s",
+                  DEV_CON_DATA_LOG_ARGS_DATA(data),
+                  nm_settings_autoconnect_blocked_reason_to_string(v, buf, sizeof(buf)));
+            data->autoconnect.blocked_reason = v;
+            changed                          = TRUE;
+        }
+        return changed;
+    }
+
+    if (device) {
+        data = _devcon_lookup_data(self, device, sett_conn, TRUE, TRUE);
+        v    = data->autoconnect.blocked_reason;
+        v    = NM_FLAGS_ASSIGN(v, value, set);
+
+        if (data->autoconnect.blocked_reason == v)
+            return FALSE;
+
+        data->autoconnect.blocked_reason = v;
+        _LOGT(LOGD_SETTINGS,
+              "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": set blocked reason %s",
+              DEV_CON_DATA_LOG_ARGS_DATA(data),
+              nm_settings_autoconnect_blocked_reason_to_string(v, buf, sizeof(buf)));
+        return TRUE;
+    }
+
+    c_list_for_each_entry (data, &sett_conn->devcon_con_lst_head, con_lst) {
+        v = data->autoconnect.blocked_reason;
+        v = NM_FLAGS_ASSIGN(v, value, set);
+
+        if (data->autoconnect.blocked_reason == v)
+            continue;
+
+        _LOGT(LOGD_SETTINGS,
+              "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": set blocked reason %s",
+              DEV_CON_DATA_LOG_ARGS_DATA(data),
+              nm_settings_autoconnect_blocked_reason_to_string(v, buf, sizeof(buf)));
+        data->autoconnect.blocked_reason = v;
+        changed                          = TRUE;
+    }
+
+    return changed;
+}
+
+/*****************************************************************************/
+
+static guint
+_devcon_data_hash(gconstpointer ptr)
+{
+    const DevConData *data = ptr;
+
+    nm_assert(NM_IS_DEVICE(data->device));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(data->sett_conn));
+
+    return nm_hash_vals(1832112199u, data->device, data->sett_conn);
+}
+
+static gboolean
+_devcon_data_equal(gconstpointer ptr_a, gconstpointer ptr_b)
+{
+    const DevConData *data_a = ptr_a;
+    const DevConData *data_b = ptr_b;
+
+    nm_assert(NM_IS_DEVICE(data_a->device));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(data_a->sett_conn));
+    nm_assert(NM_IS_DEVICE(data_b->device));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(data_b->sett_conn));
+
+    return data_a->device == data_b->device && data_a->sett_conn == data_b->sett_conn;
+}
+
+static DevConData *
+_devcon_lookup_data(NMManager            *self,
+                    NMDevice             *device,
+                    NMSettingsConnection *sett_conn,
+                    gboolean              create,
+                    gboolean              log_creation)
+{
+    NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self);
+    DevConData       *data;
+    DevConData        needle;
+
+    nm_assert(NM_IS_DEVICE(device));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+    nm_assert(self == nm_device_get_manager(device));
+    nm_assert(self == nm_settings_connection_get_manager(sett_conn));
+
+    needle.device    = device;
+    needle.sett_conn = sett_conn;
+
+    data = g_hash_table_lookup(priv->devcon_data_dict, &needle);
+
+    if (data)
+        return data;
+    if (!create)
+        return NULL;
+
+    data  = g_slice_new(DevConData);
+    *data = (DevConData){
+        .device    = device,
+        .sett_conn = sett_conn,
+        .autoconnect =
+            {
+                .initialized       = FALSE,
+                .retries           = 0,
+                .blocked_until_sec = 0,
+                .blocked_reason    = NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE,
+            },
+    };
+    c_list_link_tail(&device->devcon_dev_lst_head, &data->dev_lst);
+    c_list_link_tail(&sett_conn->devcon_con_lst_head, &data->con_lst);
+
+    g_hash_table_add(priv->devcon_data_dict, data);
+
+    if (log_creation) {
+        _LOGT(LOGD_SETTINGS,
+              "block-autoconnect: " DEV_CON_DATA_LOG_FMT ": entry created (not initialized)",
+              DEV_CON_DATA_LOG_ARGS_DATA(data));
+    }
+
+    return data;
+}
+
+static void
+_devcon_remove_data(NMManager *self, DevConData *data)
+{
+    NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE(self);
+
+    nm_assert(data);
+    nm_assert(NM_IS_DEVICE(data->device));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(data->sett_conn));
+    nm_assert(data == _devcon_lookup_data(self, data->device, data->sett_conn, FALSE, FALSE));
+
+    c_list_unlink_stale(&data->dev_lst);
+    c_list_unlink_stale(&data->con_lst);
+    g_hash_table_remove(priv->devcon_data_dict, data);
+    nm_g_slice_free(data);
+}
+
+static gboolean
+_devcon_remove_device_all(NMManager *self, NMDevice *device)
+{
+    DevConData *data;
+    gboolean    changed;
+
+    nm_assert(NM_IS_DEVICE(device));
+
+    while ((data = c_list_first_entry(&device->devcon_dev_lst_head, DevConData, dev_lst))) {
+        changed = TRUE;
+        _devcon_remove_data(self, data);
+    }
+
+    return changed;
+}
+
+static gboolean
+_devcon_remove_sett_conn_all(NMManager *self, NMSettingsConnection *sett_conn)
+{
+    DevConData *data;
+    gboolean    changed = FALSE;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+
+    while ((data = c_list_first_entry(&sett_conn->devcon_con_lst_head, DevConData, con_lst))) {
+        changed = TRUE;
+        _devcon_remove_data(self, data);
+    }
+
+    return changed;
+}
+
+void
+nm_manager_notify_delete_settings_connections(NMManager *self, NMSettingsConnection *sett_conn)
+{
+    _devcon_remove_sett_conn_all(self, sett_conn);
+}
+
+/*****************************************************************************/
+
 static void
 _config_changed_cb(NMConfig           *config,
                    NMConfigData       *config_data,
@@ -1429,7 +1934,7 @@ find_device_by_iface(NMManager    *self,
     c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
         if (!nm_streq(nm_device_get_iface(candidate), iface))
             continue;
-        if (connection && !nm_device_check_connection_compatible(candidate, connection, NULL))
+        if (connection && !nm_device_check_connection_compatible(candidate, connection, TRUE, NULL))
             continue;
         if (slave) {
             if (!nm_device_is_master(candidate))
@@ -1804,7 +2309,7 @@ remove_device(NMManager *self, NMDevice *device, gboolean quitting)
                 nm_device_sys_iface_state_set(device, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
                 nm_device_set_unmanaged_by_flags(device,
                                                  NM_UNMANAGED_PLATFORM_INIT,
-                                                 TRUE,
+                                                 NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                                  NM_DEVICE_STATE_REASON_REMOVED);
             }
         }
@@ -1814,6 +2319,8 @@ remove_device(NMManager *self, NMDevice *device, gboolean quitting)
 
     nm_settings_device_removed(priv->settings, device, quitting);
 
+    _devcon_remove_device_all(self, device);
+
     c_list_unlink(&device->devices_lst);
 
     _parent_notify_changed(self, device, TRUE);
@@ -1931,6 +2438,7 @@ find_parent_device_for_connection(NMManager       *self,
             && nm_device_check_connection_compatible(
                 candidate,
                 nm_settings_connection_get_connection(parent_connection),
+                TRUE,
                 NULL))
             first_compatible = candidate;
     }
@@ -2088,9 +2596,10 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
     guint                        i;
     gs_free char                *iface = NULL;
     const char                  *parent_spec;
-    NMDevice                    *device = NULL, *parent = NULL;
+    NMDevice                    *device = NULL;
+    NMDevice                    *parent = NULL;
     NMDevice                    *dev_candidate;
-    GError                      *error = NULL;
+    gs_free_error GError        *error = NULL;
     NMLogLevel                   log_level;
 
     g_return_val_if_fail(NM_IS_MANAGER(self), NULL);
@@ -2099,7 +2608,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
     iface = nm_manager_get_connection_iface(self, connection, &parent, &parent_spec, &error);
     if (!iface) {
         _LOG3D(LOGD_DEVICE, connection, "can't get a name of a virtual device: %s", error->message);
-        g_error_free(error);
         return NULL;
     }
 
@@ -2110,7 +2618,7 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
 
     /* See if there's a device that is already compatible with this connection */
     c_list_for_each_entry (dev_candidate, &priv->devices_lst_head, devices_lst) {
-        if (nm_device_check_connection_compatible(dev_candidate, connection, NULL)) {
+        if (nm_device_check_connection_compatible(dev_candidate, connection, FALSE, NULL)) {
             if (nm_device_is_real(dev_candidate)) {
                 _LOG3D(LOGD_DEVICE, connection, "already created virtual interface name %s", iface);
                 return NULL;
@@ -2137,7 +2645,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
         device = nm_device_factory_create_device(factory, iface, NULL, connection, NULL, &error);
         if (!device) {
             _LOG3W(LOGD_DEVICE, connection, "factory can't create the device: %s", error->message);
-            g_error_free(error);
             return NULL;
         }
 
@@ -2148,7 +2655,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
                    connection,
                    "can't register the device with manager: %s",
                    error->message);
-            g_error_free(error);
             g_object_unref(device);
             return NULL;
         }
@@ -2169,7 +2675,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
 
     if (!find_master(self, connection, device, NULL, NULL, NULL, &error)) {
         _LOG3D(LOGD_DEVICE, connection, "skip activation: %s", error->message);
-        g_error_free(error);
         return device;
     }
 
@@ -2179,11 +2684,10 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
         NMConnection        *candidate = nm_settings_connection_get_connection(connections[i]);
         NMSettingConnection *s_con;
 
-        if (!nm_device_check_connection_compatible(device, candidate, NULL))
+        if (!nm_device_check_connection_compatible(device, candidate, TRUE, NULL))
             continue;
 
         s_con = nm_connection_get_setting_connection(candidate);
-        g_assert(s_con);
         if (!nm_setting_connection_get_autoconnect(s_con)
             || nm_settings_connection_autoconnect_is_blocked(connections[i]))
             continue;
@@ -2199,7 +2703,6 @@ system_create_virtual_device(NMManager *self, NMConnection *connection)
                     connection,
                     "couldn't create the device: %s",
                     error->message);
-            g_error_free(error);
             return NULL;
         }
 
@@ -2666,6 +3169,16 @@ _rfkill_update_from_user(NMManager *self, NMRfkillType rtype, gboolean enabled)
 
 /*****************************************************************************/
 
+void
+nm_manager_device_recheck_auto_activate_schedule(NMManager *self, NMDevice *device)
+{
+    g_return_if_fail(NM_IS_MANAGER(self));
+
+    nm_policy_device_recheck_auto_activate_schedule(NM_MANAGER_GET_PRIVATE(self)->policy, device);
+}
+
+/*****************************************************************************/
+
 static void
 device_auth_done_cb(NMAuthChain *chain, GDBusMethodInvocation *context, gpointer user_data)
 {
@@ -2821,7 +3334,7 @@ new_activation_allowed_for_connection(NMManager *self, NMSettingsConnection *con
  * get_existing_connection:
  * @manager: #NMManager instance
  * @device: #NMDevice instance
- * @out_generated: (allow-none): return TRUE, if the connection was generated.
+ * @out_generated: (out) (optional): return TRUE, if the connection was generated.
  *
  * Returns: a #NMSettingsConnection to be assumed by the device, or %NULL if
  *   the device does not support assuming existing connections.
@@ -2882,11 +3395,12 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat
         }
     }
 
-    if (nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA,
-                                                 NM_CONFIG_KEYFILE_KEY_DEVICE_KEEP_CONFIGURATION,
-                                                 device,
-                                                 TRUE,
-                                                 TRUE)) {
+    if (nm_config_data_get_device_config_boolean_by_device(
+            NM_CONFIG_GET_DATA,
+            NM_CONFIG_KEYFILE_KEY_DEVICE_KEEP_CONFIGURATION,
+            device,
+            TRUE,
+            TRUE)) {
         /* The core of the API is nm_device_generate_connection() function, based on
          * update_connection() virtual method and the @connection_type_supported
          * class attribute. Devices that support assuming existing connections must
@@ -2930,6 +3444,7 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat
         && nm_device_check_connection_compatible(
             device,
             nm_settings_connection_get_connection(connection_checked),
+            TRUE,
             NULL)) {
         if (connection) {
             NMConnection *con = nm_settings_connection_get_connection(connection_checked);
@@ -2968,6 +3483,7 @@ get_existing_connection(NMManager *self, NMDevice *device, gboolean *out_generat
                     && nm_device_check_connection_compatible(
                         device,
                         nm_settings_connection_get_connection(sett_conn),
+                        TRUE,
                         NULL))
                     sett_conns[j++] = sett_conn;
             }
@@ -3448,15 +3964,15 @@ _device_realize_finish(NMManager *self, NMDevice *device, const NMPlatformLink *
      * is still unavailable. Set UNAVAILABLE state again, this time with NOW_MANAGED. */
     nm_device_state_changed(device,
                             NM_DEVICE_STATE_UNAVAILABLE,
-                            NM_DEVICE_STATE_REASON_NOW_MANAGED);
-    nm_device_emit_recheck_auto_activate(device);
+                            nm_device_get_manage_reason_external(device));
+    nm_manager_device_recheck_auto_activate_schedule(self, device);
 }
 
 /**
  * add_device:
  * @self: the #NMManager
  * @device: the #NMDevice to add
- * @error: (out): the #GError
+ * @error: the #GError
  *
  * If successful, this function will increase the references count of @device.
  * Callers should decrease the reference count.
@@ -3813,7 +4329,7 @@ _check_remove_dev_on_link_deleted(NMManager *self, NMDevice *device)
                          NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED))
             continue;
 
-        if (!nm_device_check_connection_compatible(device, con, NULL))
+        if (!nm_device_check_connection_compatible(device, con, TRUE, NULL))
             continue;
 
         /* Found a virtual connection compatible, the device must
@@ -4390,64 +4906,136 @@ find_master(NMManager             *self,
             NMActiveConnection   **out_master_ac,
             GError               **error)
 {
-    NMManagerPrivate     *priv = NM_MANAGER_GET_PRIVATE(self);
-    NMSettingConnection  *s_con;
-    const char           *master;
-    NMDevice             *master_device = NULL;
-    NMSettingsConnection *master_connection;
+    NMManagerPrivate            *priv = NM_MANAGER_GET_PRIVATE(self);
+    NMSettingConnection         *s_con;
+    const char                  *master;
+    NMDevice                    *master_device     = NULL;
+    NMSettingsConnection        *master_connection = NULL;
+    NMSettingsConnection *const *connections;
+    guint                        i;
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    nm_assert(!out_master_connection || !*out_master_connection);
+    nm_assert(!out_master_device || !*out_master_device);
+    nm_assert(!out_master_ac || !*out_master_ac);
+
+    s_con  = nm_connection_get_setting_connection(connection);
     master = nm_setting_connection_get_master(s_con);
 
     if (master == NULL)
         return TRUE; /* success, but no master */
 
-    /* Try as an interface name first */
-    master_device = find_device_by_iface(self, master, NULL, connection);
-    if (master_device) {
-        if (master_device == device) {
-            g_set_error_literal(error,
-                                NM_MANAGER_ERROR,
-                                NM_MANAGER_ERROR_DEPENDENCY_FAILED,
-                                "Device cannot be its own master");
-            return FALSE;
+    _LOGD(LOGD_CORE,
+          "Looking for a master '%s' for connection '%s' (%s)",
+          master,
+          nm_connection_get_id(connection),
+          nm_connection_get_uuid(connection));
+
+    connections = nm_settings_get_connections_sorted_by_autoconnect_priority(priv->settings, NULL);
+    for (i = 0; connections[i]; i++) {
+        NMConnection *master_candidate = nm_settings_connection_get_connection(connections[i]);
+        NMDevice     *device_candidate;
+
+        if (nm_streq(nm_connection_get_uuid(master_candidate), master)) {
+            if (!is_compatible_with_slave(master_candidate, connection)) {
+                g_set_error(error,
+                            NM_MANAGER_ERROR,
+                            NM_MANAGER_ERROR_DEPENDENCY_FAILED,
+                            "The active connection on %s is not compatible",
+                            nm_device_get_iface(master_device));
+                return FALSE;
+            }
+
+            _LOGD(LOGD_CORE,
+                  "Will consider using connection '%s' (%s) as a master for '%s' (%s) "
+                  "because UUID matches",
+                  nm_connection_get_id(master_candidate),
+                  nm_connection_get_uuid(master_candidate),
+                  nm_connection_get_id(connection),
+                  nm_connection_get_uuid(connection));
+
+            master_connection = connections[i];
+        } else if (nm_connection_get_interface_name(master_candidate)
+                   && nm_streq(nm_connection_get_interface_name(master_candidate), master)) {
+            if (!is_compatible_with_slave(master_candidate, connection))
+                continue;
+
+            /* This might be good enough unless we find a better one (already active or UUID match) */
+            if (!master_connection) {
+                master_connection = connections[i];
+                _LOGD(LOGD_CORE,
+                      "Will consider using connection '%s' (%s) as a master for '%s' (%s) "
+                      "because device matches",
+                      nm_connection_get_id(master_candidate),
+                      nm_connection_get_uuid(master_candidate),
+                      nm_connection_get_id(connection),
+                      nm_connection_get_uuid(connection));
+            }
+        } else {
+            /* No match. */
+            continue;
         }
 
-        master_connection = nm_device_get_settings_connection(master_device);
-        if (master_connection
-            && !is_compatible_with_slave(nm_settings_connection_get_connection(master_connection),
-                                         connection)) {
+        /* Check if the master connection is activated on some device already */
+        c_list_for_each_entry (device_candidate, &priv->devices_lst_head, devices_lst) {
+            if (device_candidate == device)
+                continue;
+
+            if (nm_device_get_settings_connection(device_candidate) == connections[i]) {
+                master_device     = device_candidate;
+                master_connection = connections[i];
+                break;
+            }
+        }
+
+        if (master_device) {
+            /* Now we got a connection and also a device. Look no further. */
+            _LOGD(LOGD_CORE,
+                  "Will use connection '%s' (%s) as a master for '%s' (%s)",
+                  nm_connection_get_id(master_candidate),
+                  nm_connection_get_uuid(master_candidate),
+                  nm_connection_get_id(connection),
+                  nm_connection_get_uuid(connection));
+
+            break;
+        }
+    }
+
+    if (!master_connection) {
+        master_device = find_device_by_iface(self, master, NULL, connection);
+        if (!master_device) {
             g_set_error(error,
                         NM_MANAGER_ERROR,
                         NM_MANAGER_ERROR_DEPENDENCY_FAILED,
-                        "The active connection on %s is not compatible",
-                        nm_device_get_iface(master_device));
+                        "Connection or device %s not found",
+                        master);
             return FALSE;
         }
-    } else {
-        /* Try master as a connection UUID */
-        master_connection = nm_settings_get_connection_by_uuid(priv->settings, master);
-        if (master_connection) {
-            NMDevice *candidate;
-
-            /* Check if the master connection is activated on some device already */
-            c_list_for_each_entry (candidate, &priv->devices_lst_head, devices_lst) {
-                if (candidate == device)
-                    continue;
 
-                if (nm_device_get_settings_connection(candidate) == master_connection) {
-                    master_device = candidate;
-                    break;
-                }
-            }
+        if (master_device == device) {
+            g_set_error_literal(error,
+                                NM_MANAGER_ERROR,
+                                NM_MANAGER_ERROR_DEPENDENCY_FAILED,
+                                "Device cannot be its own master");
+            return FALSE;
         }
+
+        _LOGD(LOGD_CORE,
+              "Master connection for '%s' (%s) not found, will use device '%s'",
+              nm_connection_get_id(connection),
+              nm_connection_get_uuid(connection),
+              nm_device_get_iface(master_device));
+    }
+
+    if (!master_device && !master_connection) {
+        g_set_error_literal(error,
+                            NM_MANAGER_ERROR,
+                            NM_MANAGER_ERROR_UNKNOWN_DEVICE,
+                            "Master connection not found or invalid");
+        return FALSE;
     }
 
-    if (out_master_connection)
-        *out_master_connection = master_connection;
-    if (out_master_device)
-        *out_master_device = master_device;
+    NM_SET_OUT(out_master_connection, master_connection);
+    NM_SET_OUT(out_master_device, master_device);
     if (out_master_ac && master_connection) {
         *out_master_ac = active_connection_find(self,
                                                 master_connection,
@@ -4457,15 +5045,7 @@ find_master(NMManager             *self,
                                                 NULL);
     }
 
-    if (master_device || master_connection)
-        return TRUE;
-    else {
-        g_set_error_literal(error,
-                            NM_MANAGER_ERROR,
-                            NM_MANAGER_ERROR_UNKNOWN_DEVICE,
-                            "Master connection not found or invalid");
-        return FALSE;
-    }
+    return TRUE;
 }
 
 /**
@@ -4632,7 +5212,9 @@ ensure_master_active_connection(NMManager            *self,
                 continue;
 
             if (nm_device_is_real(candidate)
-                && nm_device_get_state(candidate) != NM_DEVICE_STATE_DISCONNECTED)
+                && !NM_IN_SET(nm_device_get_state(candidate),
+                              NM_DEVICE_STATE_DISCONNECTED,
+                              NM_DEVICE_STATE_DEACTIVATING))
                 continue;
 
             master_ac = nm_manager_activate_connection(
@@ -4710,8 +5292,9 @@ find_slaves(NMManager            *manager,
                                                                    &n_all_connections);
     for (i = 0; i < n_all_connections; i++) {
         NMSettingsConnection *master_connection = NULL;
-        NMDevice             *master_device     = NULL, *slave_device;
-        NMSettingsConnection *candidate         = all_connections[i];
+        NMDevice             *master_device     = NULL;
+        NMDevice             *slave_device;
+        NMSettingsConnection *candidate = all_connections[i];
 
         find_master(manager,
                     nm_settings_connection_get_connection(candidate),
@@ -4934,12 +5517,12 @@ unmanaged_to_disconnected(NMDevice *device)
      * and force the device to be managed. */
     nm_device_set_unmanaged_by_flags(device,
                                      NM_UNMANAGED_PLATFORM_INIT,
-                                     FALSE,
+                                     NM_UNMAN_FLAG_OP_SET_MANAGED,
                                      NM_DEVICE_STATE_REASON_USER_REQUESTED);
 
     nm_device_set_unmanaged_by_flags(device,
                                      NM_UNMANAGED_USER_EXPLICIT,
-                                     FALSE,
+                                     NM_UNMAN_FLAG_OP_SET_MANAGED,
                                      NM_DEVICE_STATE_REASON_USER_REQUESTED);
 
     if (!nm_device_get_managed(device, FALSE)) {
@@ -5042,9 +5625,66 @@ active_connection_parent_active(NMActiveConnection *active,
 }
 
 static gboolean
+_check_autoconnect_port(NMActiveConnection   *active,
+                        NMSettingsConnection *master_connection,
+                        NMDevice             *master_device,
+                        NMActiveConnection   *master_ac)
+{
+    NMSettingConnection *s_con;
+    NMDevice            *device;
+
+    if (nm_active_connection_get_activation_reason(active) != NM_ACTIVATION_REASON_AUTOCONNECT) {
+        /* This is an explicit activation. Proceed. */
+        return TRUE;
+    }
+
+    if (!master_connection) {
+        /* This is not a port. Proceed. */
+        return TRUE;
+    }
+
+    device = nm_active_connection_get_device(active);
+
+    if (!nm_device_is_real(device)) {
+        /* The device is not real. We don't know about the carrier. Proceed. */
+        return TRUE;
+    }
+
+    if (nm_device_get_ifindex(device) <= 0) {
+        /* The device has no ifindex. It has no concept of carrier. Proceed. */
+        return TRUE;
+    }
+
+    if (nm_device_has_carrier(device)) {
+        /* The device has carrier. Proceed. */
+        return TRUE;
+    }
+
+    s_con = nm_settings_connection_get_setting(master_connection, NM_META_SETTING_TYPE_CONNECTION);
+
+    if (nm_setting_connection_get_autoconnect(s_con)) {
+        /* The controller profile has autoconnect enabled. Here we want to honor
+         * "ignore-carrier=no", which -- as configuration -- only makes sense for
+         * controllers that have autoconnect disable. Proceed. */
+        return TRUE;
+    }
+
+    if (nm_config_data_get_ignore_carrier_for_port(
+            NM_CONFIG_GET_DATA,
+            nm_setting_connection_get_interface_name(s_con),
+            nm_setting_connection_get_connection_type(s_con))) {
+        /* We ignore carrier on the master (as we would do by default). Proceed. */
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static gboolean
 _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **error)
 {
-    NMDevice                *device, *master_device = NULL;
+    NMDevice                *device;
+    NMDevice                *master_device = NULL;
     NMConnection            *applied;
     NMSettingsConnection    *sett_conn;
     NMSettingsConnection    *master_connection   = NULL;
@@ -5120,6 +5760,37 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
         return FALSE;
     }
 
+    /* FIXME: in _check_autoconnect_port() we decide on whether to abort to
+     * activation based on the device's carrier state (and the controller's
+     * ignore-carrier setting).
+     *
+     * At this stage, we might be activating a VLAN attached to a bond
+     * interface. But the VLAN interface may not be created yet, and not have a
+     * carrier state yet.
+     *
+     * We could fix this, by checking again (or exclusively) before attaching
+     * the port to the controller, whether the conditions from
+     * _check_autoconnect_port() hold.  And if they don't, abort activation at
+     * a later stage.
+     *
+     * The problem is, that we already start activating the controller at this
+     * point. Hence, aborting later is not good. What instead maybe should be
+     * done, is that port profiles don't start activating the controller
+     * before they have layer 2 set up.
+     */
+
+    if (!_check_autoconnect_port(active, master_connection, master_device, master_ac)) {
+        /* Usually, port and controller devices can (auto)connect without carrier. However,
+         * the controller has "ignore-carrier=no" configured. If the port autoconnects,
+         * has no carrier and the controller has ignore-carrier=no, then autoconnect
+         * is going to fail. */
+        g_set_error(error,
+                    NM_MANAGER_ERROR,
+                    NM_MANAGER_ERROR_DEPENDENCY_FAILED,
+                    "port has no carrier and controller does not ignore carrier");
+        return FALSE;
+    }
+
     /* Create any backing resources the device needs */
     if (!nm_device_is_real(device)) {
         NMDevice *parent;
@@ -5151,7 +5822,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
             if (nm_active_connection_get_activation_reason(active)
                     == NM_ACTIVATION_REASON_AUTOCONNECT
                 && NM_FLAGS_HAS(nm_settings_connection_autoconnect_blocked_reason_get(parent_con),
-                                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST)) {
+                                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST)) {
                 g_set_error(error,
                             NM_MANAGER_ERROR,
                             NM_MANAGER_ERROR_DEPENDENCY_FAILED,
@@ -5529,11 +6200,60 @@ fail:
                                         error_desc ?: error->message);
 }
 
+void
+nm_manager_deactivate_ac(NMManager *self, NMSettingsConnection *connection)
+{
+    NMManagerPrivate   *priv = NM_MANAGER_GET_PRIVATE(self);
+    NMActiveConnection *ac;
+    const CList        *tmp_list, *tmp_safe;
+    GError             *error = NULL;
+    AsyncOpData        *async_op_data;
+    AsyncOpData        *async_op_data_safe;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(connection));
+
+    nm_manager_for_each_active_connection_safe (self, ac, tmp_list, tmp_safe) {
+        if (nm_active_connection_get_settings_connection(ac) == connection
+            && (nm_active_connection_get_state(ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) {
+            if (!nm_manager_deactivate_connection(self,
+                                                  ac,
+                                                  NM_DEVICE_STATE_REASON_CONNECTION_REMOVED,
+                                                  &error)) {
+                _LOGW(LOGD_DEVICE,
+                      "connection '%s' disappeared, but error deactivating it: (%d) %s",
+                      nm_settings_connection_get_id(connection),
+                      error ? error->code : -1,
+                      error ? error->message : "(unknown)");
+                g_clear_error(&error);
+            }
+        }
+    }
+
+    c_list_for_each_entry_safe (async_op_data,
+                                async_op_data_safe,
+                                &priv->async_op_lst_head,
+                                async_op_lst) {
+        if (!NM_IN_SET(async_op_data->async_op_type,
+                       ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_INTERNAL,
+                       ASYNC_OP_TYPE_AC_AUTH_ACTIVATE_USER,
+                       ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE,
+                       ASYNC_OP_TYPE_AC_AUTH_ADD_AND_ACTIVATE2))
+            continue;
+
+        ac = async_op_data->ac_auth.active;
+        if (nm_active_connection_get_settings_connection(ac) == connection) {
+            nm_active_connection_set_state(ac,
+                                           NM_ACTIVE_CONNECTION_STATE_DEACTIVATED,
+                                           NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED);
+        }
+    }
+}
+
 /**
  * nm_manager_activate_connection():
  * @self: the #NMManager
  * @sett_conn: the #NMSettingsConnection to activate on @device
- * @applied: (allow-none): the applied connection to activate on @device
+ * @applied: (nullable): the applied connection to activate on @device
  * @specific_object: the specific object path, if any, for the activation
  * @device: the #NMDevice to activate @sett_conn on. Can be %NULL for VPNs.
  * @subject: the subject which requested activation
@@ -5782,7 +6502,7 @@ _activation_auth_done(NMManager             *self,
 
     nm_settings_connection_autoconnect_blocked_reason_set(
         connection,
-        NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+        NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST,
         FALSE);
     g_dbus_method_invocation_return_value(
         invocation,
@@ -6492,7 +7212,7 @@ device_sleep_cb(NMDevice *device, GParamSpec *pspec, NMManager *self)
         _LOGD(LOGD_SUSPEND, "sleep: unmanaging device %s", nm_device_get_ip_iface(device));
         nm_device_set_unmanaged_by_flags_queue(device,
                                                NM_UNMANAGED_SLEEPING,
-                                               TRUE,
+                                               NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                                NM_DEVICE_STATE_REASON_SLEEPING);
         break;
     case NM_DEVICE_STATE_UNMANAGED:
@@ -6541,6 +7261,8 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
                 continue;
             }
 
+            nm_device_notify_sleeping(device);
+
             if (nm_device_is_activating(device)
                 || nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) {
                 _LOGD(LOGD_SUSPEND,
@@ -6554,7 +7276,7 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
             } else {
                 nm_device_set_unmanaged_by_flags(device,
                                                  NM_UNMANAGED_SLEEPING,
-                                                 TRUE,
+                                                 NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                                  NM_DEVICE_STATE_REASON_SLEEPING);
             }
         }
@@ -6574,7 +7296,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,
-                                                     TRUE,
+                                                     NM_UNMAN_FLAG_OP_SET_UNMANAGED,
                                                      NM_DEVICE_STATE_REASON_SLEEPING);
 
                 /* Check if the device is unmanaged but the state transition is still pending.
@@ -6597,7 +7319,8 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
 
         /* Re-manage managed devices */
         c_list_for_each_entry (device, &priv->devices_lst_head, devices_lst) {
-            guint i;
+            NMDeviceStateReason reason;
+            guint               i;
 
             if (nm_device_is_software(device)
                 && !nm_device_get_unmanaged_flags(device, NM_UNMANAGED_SLEEPING)) {
@@ -6628,10 +7351,17 @@ do_sleep_wake(NMManager *self, gboolean sleeping_changed)
                     nm_device_set_enabled(device, enabled);
             }
 
+            /* The reason determines whether the device will be sys-iface-state=managed
+             * or sys-iface-state=external. Pass the correct reason to restore the state
+             * that was set before sleeping. */
+            reason = nm_device_get_sys_iface_state_before_sleep(device)
+                             == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL
+                         ? NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED
+                         : NM_DEVICE_STATE_REASON_NOW_MANAGED;
             nm_device_set_unmanaged_by_flags(device,
                                              NM_UNMANAGED_SLEEPING,
-                                             FALSE,
-                                             NM_DEVICE_STATE_REASON_NOW_MANAGED);
+                                             NM_UNMAN_FLAG_OP_SET_MANAGED,
+                                             reason);
         }
 
         /* Give the connections a chance to recreate the virtual devices.
@@ -7963,6 +8693,14 @@ nm_settings_get(void)
     return NM_MANAGER_GET_PRIVATE(singleton_instance)->settings;
 }
 
+NMPolicy *
+nm_manager_get_policy(NMManager *self)
+{
+    g_return_val_if_fail(NM_IS_MANAGER(self), NULL);
+
+    return NM_MANAGER_GET_PRIVATE(self)->policy;
+}
+
 NMManager *
 nm_manager_setup(void)
 {
@@ -8106,6 +8844,8 @@ nm_manager_init(NMManager *self)
     priv->state    = NM_STATE_DISCONNECTED;
     priv->startup  = TRUE;
 
+    priv->devcon_data_dict = g_hash_table_new(_devcon_data_hash, _devcon_data_equal);
+
     /* sleep/wake handling */
     priv->sleep_monitor = nm_sleep_monitor_new();
     g_signal_connect(priv->sleep_monitor, NM_SLEEP_MONITOR_SLEEPING, G_CALLBACK(sleeping_cb), self);
@@ -8439,6 +9179,8 @@ dispose(GObject *object)
 
     nm_clear_pointer(&priv->device_route_metrics, g_hash_table_destroy);
 
+    nm_clear_pointer(&priv->devcon_data_dict, g_hash_table_destroy);
+
     G_OBJECT_CLASS(nm_manager_parent_class)->dispose(object);
 }
 
diff --git a/src/core/nm-manager.h b/src/core/nm-manager.h
index 0cbbcbf0..3028eb7e 100644
--- a/src/core/nm-manager.h
+++ b/src/core/nm-manager.h
@@ -69,6 +69,8 @@ NMManager *nm_manager_setup(void);
 NMManager *nm_manager_get(void);
 #define NM_MANAGER_GET (nm_manager_get())
 
+NMPolicy *nm_manager_get_policy(NMManager *self);
+
 gboolean nm_manager_start(NMManager *manager, GError **error);
 void     nm_manager_stop(NMManager *manager);
 NMState  nm_manager_get_state(NMManager *manager);
@@ -121,6 +123,10 @@ NMSettingsConnection **nm_manager_get_activatable_connections(NMManager *manager
                                                               gboolean   sort,
                                                               guint     *out_len);
 
+void nm_manager_deactivate_ac(NMManager *self, NMSettingsConnection *connection);
+
+void nm_manager_device_recheck_auto_activate_schedule(NMManager *self, NMDevice *device);
+
 void     nm_manager_write_device_state_all(NMManager *manager);
 gboolean nm_manager_write_device_state(NMManager *manager, NMDevice *device, int *out_ifindex);
 
@@ -207,6 +213,11 @@ struct _NMDnsManager *nm_manager_get_dns_manager(NMManager *self);
 
 /*****************************************************************************/
 
+void nm_manager_notify_delete_settings_connections(NMManager            *self,
+                                                   NMSettingsConnection *sett_conn);
+
+/*****************************************************************************/
+
 void nm_manager_device_auth_request(NMManager                     *self,
                                     NMDevice                      *device,
                                     GDBusMethodInvocation         *context,
@@ -219,4 +230,40 @@ void nm_manager_device_auth_request(NMManager                     *self,
 
 void nm_manager_unblock_failed_ovs_interfaces(NMManager *self);
 
+/*****************************************************************************/
+
+#define NM_AUTOCONNECT_RETRIES_FOREVER G_MAXUINT32
+
+guint32 nm_manager_devcon_autoconnect_retries_get(NMManager            *self,
+                                                  NMDevice             *device,
+                                                  NMSettingsConnection *sett_conn);
+
+void nm_manager_devcon_autoconnect_retries_set(NMManager            *self,
+                                               NMDevice             *device,
+                                               NMSettingsConnection *sett_conn,
+                                               guint32               retries);
+
+gboolean nm_manager_devcon_autoconnect_retries_reset(NMManager            *self,
+                                                     NMDevice             *device,
+                                                     NMSettingsConnection *sett_conn);
+
+gboolean nm_manager_devcon_autoconnect_reset_reconnect_all(NMManager            *self,
+                                                           NMDevice             *device,
+                                                           NMSettingsConnection *sett_conn,
+                                                           gboolean              only_no_secrets);
+
+gint32 nm_manager_devcon_autoconnect_retries_blocked_until(NMManager            *self,
+                                                           NMDevice             *device,
+                                                           NMSettingsConnection *sett_conn);
+
+gboolean nm_manager_devcon_autoconnect_is_blocked(NMManager            *self,
+                                                  NMDevice             *device,
+                                                  NMSettingsConnection *sett_conn);
+
+gboolean nm_manager_devcon_autoconnect_blocked_reason_set(NMManager            *self,
+                                                          NMDevice             *device,
+                                                          NMSettingsConnection *sett_conn,
+                                                          NMSettingsAutoconnectBlockedReason value,
+                                                          gboolean                           set);
+
 #endif /* __NETWORKMANAGER_MANAGER_H__ */
diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c
index 12ca8508..ad156f99 100644
--- a/src/core/nm-netns.c
+++ b/src/core/nm-netns.c
@@ -22,6 +22,44 @@
 
 /*****************************************************************************/
 
+typedef struct {
+    gconstpointer tag;
+    CList         watcher_by_tag_lst_head;
+} WatcherByTag;
+
+typedef struct {
+    NMIPAddrTyped addr;
+    CList         watcher_ip_addr_lst_head;
+} WatcherDataIPAddr;
+
+struct _NMNetnsWatcherHandle {
+    NMNetnsWatcherType     watcher_type;
+    NMNetnsWatcherData     watcher_data;
+    gconstpointer          tag;
+    NMNetnsWatcherCallback callback;
+    gpointer               callback_user_data;
+
+    /* This is linked to "WatcherByTag.watcher_by_tag_lst_head" in
+     * "priv->watcher_by_tag_idx". */
+    CList watcher_tag_lst;
+
+    /* The registration data, which depends on the "watcher_type". */
+    union {
+        struct {
+            CList watcher_ip_addr_lst;
+        } ip_addr;
+    } reg_data;
+
+    /* nm_netns_watcher_add() will mark the handle as non-dirty, while
+     * nm_netns_watcher_remove_all() can delete only dirty handles (while
+     * leaving non-dirty handles alive, but marking them as dirty).
+     *
+     * That allows a pattern where you just add the new handles that you want
+     * now, and then call nm_netns_watcher_remove_all() to remove those that
+     * should no longer be present. */
+    bool watcher_dirty : 1;
+};
+
 NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLATFORM, );
 
 typedef struct {
@@ -33,8 +71,20 @@ typedef struct {
     GHashTable       *shared_ips;
     GHashTable       *ecmp_track_by_obj;
     GHashTable       *ecmp_track_by_ecmpid;
-    CList             l3cfg_signal_pending_lst_head;
-    GSource          *signal_pending_idle_source;
+
+    /* Indexes the watcher handles. */
+    GHashTable *watcher_idx;
+
+    /* An index of WatcherByTag. It allows to lookup watcher handles by tag.
+     * Handles without tag are not indexed. */
+    GHashTable *watcher_by_tag_idx;
+
+    /* Index for WatcherDataIPAddr instances. Allows to lookup all subscribers
+     * by IP address. */
+    GHashTable *watcher_ip_data_idx;
+
+    CList    l3cfg_signal_pending_lst_head;
+    GSource *signal_pending_idle_source;
 } NMNetnsPrivate;
 
 struct _NMNetns {
@@ -87,6 +137,24 @@ NM_DEFINE_SINGLETON_GETTER(NMNetns, nm_netns_get, NM_TYPE_NETNS);
 
 /*****************************************************************************/
 
+static WatcherDataIPAddr             *
+_watcher_ip_data_lookup(NMNetns *self, int addr_family, gconstpointer addr);
+static void _watcher_handle_notify(NMNetns                       *self,
+                                   NMNetnsWatcherHandle          *handle,
+                                   const NMNetnsWatcherEventData *event_data);
+static const char *
+_watcher_handle_to_string(const NMNetnsWatcherHandle *handle, char *buf, gsize buf_size);
+
+/*****************************************************************************/
+
+static gboolean
+NM_NETNS_WATCHER_TYPE_VALID(NMNetnsWatcherType watcher_type)
+{
+    return NM_IN_SET(watcher_type, NM_NETNS_WATCHER_TYPE_IP_ADDR);
+}
+
+/*****************************************************************************/
+
 typedef struct {
     const NMPObject *representative_obj;
     const NMPObject *merged_obj;
@@ -437,7 +505,7 @@ _platform_signal_cb(NMPlatform   *platform,
 
     l3cfg = nm_netns_l3cfg_get(self, ifindex);
     if (!l3cfg)
-        return;
+        goto notify_watcher;
 
     l3cfg->internal_netns.signal_pending_obj_type_flags |= nmp_object_type_to_flags(obj_type);
 
@@ -450,6 +518,55 @@ _platform_signal_cb(NMPlatform   *platform,
     }
 
     _nm_l3cfg_notify_platform_change(l3cfg, change_type, NMP_OBJECT_UP_CAST(platform_object));
+
+notify_watcher:
+    switch (obj_type) {
+    case NMP_OBJECT_TYPE_IP4_ADDRESS:
+    case NMP_OBJECT_TYPE_IP6_ADDRESS:
+    {
+        NMNetnsWatcherHandle *handle;
+        NMNetnsWatcherHandle *handle_safe;
+        WatcherDataIPAddr    *data;
+
+        data =
+            _watcher_ip_data_lookup(self,
+                                    obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS ? AF_INET : AF_INET6,
+                                    ((const NMPlatformIPAddress *) platform_object)->address_ptr);
+
+        if (data) {
+            const NMNetnsWatcherEventData event_data = {
+                .ip_addr =
+                    {
+                        .change_type = change_type,
+                        .obj         = NMP_OBJECT_UP_CAST(platform_object),
+                    },
+            };
+            char sbuf[500];
+
+            c_list_for_each_entry_safe (handle,
+                                        handle_safe,
+                                        &data->watcher_ip_addr_lst_head,
+                                        reg_data.ip_addr.watcher_ip_addr_lst) {
+                _LOGT("netns-watcher: %s %s",
+                      "notify",
+                      _watcher_handle_to_string(handle, sbuf, sizeof(sbuf)));
+
+                /* Note that we dispatch these events directly from the platform event
+                 * and while iterating over "data".
+                 *
+                 * From the callback, it's probably a bad idea to do anything in platform
+                 * that might change anything (emit new signals) or to nm_netns_watcher_remove*()
+                 * any other watcher.
+                 *
+                 * The callee needs to be careful. */
+                _watcher_handle_notify(self, handle, &event_data);
+            }
+        }
+        break;
+    }
+    default:
+        break;
+    }
 }
 
 /*****************************************************************************/
@@ -695,7 +812,7 @@ nm_netns_ip_route_ecmp_commit(NMNetns    *self,
                     /* This route is onlink. We don't need to configure an onlink route
                      * to the gateway, and the route is immediately ready for configuration. */
                     track_obj->is_ready = TRUE;
-                } else if (c_list_length_is(&track_ecmpid->ecmpid_lst_head, 1)) {
+                } else if (c_list_is_empty_or_single(&track_ecmpid->ecmpid_lst_head)) {
                     /* This route has no merge partner and ends up being a
                      * single hop route. It will be returned and configured by
                      * the calling "l3cfg".
@@ -815,7 +932,461 @@ nm_netns_ip_route_ecmp_commit(NMNetns    *self,
         if (changed || is_reapply) {
             _LOGT("ecmp-route: multi-hop %s",
                   nmp_object_to_string(route_obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
-            nm_platform_ip_route_add(priv->platform, NMP_NLM_FLAG_APPEND, route_obj);
+            nm_platform_ip_route_add(priv->platform, NMP_NLM_FLAG_APPEND, route_obj, NULL);
+        }
+    }
+}
+
+/*****************************************************************************/
+
+static void
+_watcher_data_set(NMNetnsWatcherData       *dst,
+                  NMNetnsWatcherType        watcher_type,
+                  const NMNetnsWatcherData *src)
+{
+    nm_assert(dst);
+    nm_assert(src);
+
+    switch (watcher_type) {
+    case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+        dst->ip_addr = src->ip_addr;
+        return;
+    }
+    nm_assert_not_reached();
+}
+
+static void
+_watcher_data_hash(NMHashState *h, NMNetnsWatcherType watcher_type, const NMNetnsWatcherData *data)
+{
+    nm_assert(h);
+    nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type));
+    nm_assert(data);
+
+    switch (watcher_type) {
+    case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+        nm_ip_addr_typed_hash_update(h, &data->ip_addr.addr);
+        return;
+    }
+    nm_assert_not_reached();
+}
+
+static gboolean
+_watcher_data_equal(NMNetnsWatcherType        watcher_type,
+                    const NMNetnsWatcherData *a,
+                    const NMNetnsWatcherData *b)
+{
+    nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type));
+    nm_assert(a);
+    nm_assert(b);
+
+    switch (watcher_type) {
+    case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+        return nm_ip_addr_typed_equal(&a->ip_addr.addr, &b->ip_addr.addr);
+    }
+    return nm_assert_unreachable_val(FALSE);
+}
+
+static void
+_watcher_by_tag_destroy(WatcherByTag *watcher_by_tag)
+{
+    c_list_unlink_stale(&watcher_by_tag->watcher_by_tag_lst_head);
+    nm_g_slice_free(watcher_by_tag);
+}
+
+static void
+_watcher_handle_init(NMNetnsWatcherHandle     *handle,
+                     NMNetnsWatcherType        watcher_type,
+                     const NMNetnsWatcherData *watcher_data,
+                     gconstpointer             tag)
+{
+    nm_assert(handle);
+    nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type));
+
+    *handle = (NMNetnsWatcherHandle){
+        .watcher_type    = watcher_type,
+        .tag             = tag,
+        .watcher_tag_lst = C_LIST_INIT(handle->watcher_tag_lst),
+    };
+    _watcher_data_set(&handle->watcher_data, watcher_type, watcher_data);
+}
+
+static guint
+_watcher_handle_hash(gconstpointer data)
+{
+    const NMNetnsWatcherHandle *watcher = data;
+    NMHashState                 h;
+
+    nm_assert(watcher);
+    nm_assert(watcher->tag);
+
+    nm_hash_init(&h, 2696278447u);
+    nm_hash_update_vals(&h, watcher->tag, watcher->watcher_type);
+    _watcher_data_hash(&h, watcher->watcher_type, &watcher->watcher_data);
+    return nm_hash_complete(&h);
+}
+
+static gboolean
+_watcher_handle_equal(gconstpointer a, gconstpointer b)
+{
+    const NMNetnsWatcherHandle *ha = a;
+    const NMNetnsWatcherHandle *hb = b;
+
+    nm_assert(ha);
+    nm_assert(hb);
+    nm_assert(ha->tag);
+    nm_assert(hb->tag);
+
+    if (ha == hb)
+        return TRUE;
+
+    return (ha->tag == hb->tag) && (ha->watcher_type == hb->watcher_type)
+           && _watcher_data_equal(ha->watcher_type, &ha->watcher_data, &hb->watcher_data);
+}
+
+static const char *
+_watcher_handle_to_string(const NMNetnsWatcherHandle *handle, char *buf, gsize buf_size)
+{
+    const char *buf0 = buf;
+    char        sbuf[NM_INET_ADDRSTRLEN];
+
+    nm_strbuf_append(&buf,
+                     &buf_size,
+                     "h:" NM_HASH_OBFUSCATE_PTR_FMT "[",
+                     NM_HASH_OBFUSCATE_PTR(handle));
+
+    if (handle->tag) {
+        nm_strbuf_append(&buf,
+                         &buf_size,
+                         "tag:" NM_HASH_OBFUSCATE_PTR_FMT ",",
+                         NM_HASH_OBFUSCATE_PTR(handle->tag));
+    }
+
+    switch (handle->watcher_type) {
+    case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+        nm_strbuf_append_str(&buf, &buf_size, "ip-addr:");
+        nm_strbuf_append_str(&buf,
+                             &buf_size,
+                             nm_inet_ntop(handle->watcher_data.ip_addr.addr.addr_family,
+                                          &handle->watcher_data.ip_addr.addr.addr,
+                                          sbuf));
+        goto out;
+    }
+    nm_assert_not_reached();
+    nm_strbuf_append_str(&buf, &buf_size, "unknown");
+
+out:
+    nm_strbuf_append_c(&buf, &buf_size, ']');
+    return buf0;
+}
+
+static void
+_watcher_handle_notify(NMNetns                       *self,
+                       NMNetnsWatcherHandle          *handle,
+                       const NMNetnsWatcherEventData *event_data)
+{
+    nm_assert(NM_IS_NETNS(self));
+    nm_assert(handle);
+    nm_assert(handle->callback);
+
+    handle->callback(self,
+                     handle->watcher_type,
+                     &handle->watcher_data,
+                     handle->tag,
+                     event_data,
+                     handle->callback_user_data);
+}
+
+static WatcherDataIPAddr *
+_watcher_ip_data_lookup(NMNetns *self, int addr_family, gconstpointer addr)
+{
+    WatcherDataIPAddr needle;
+
+    needle.addr.addr_family = addr_family;
+    nm_ip_addr_set(addr_family, &needle.addr.addr, addr);
+    return g_hash_table_lookup(NM_NETNS_GET_PRIVATE(self)->watcher_ip_data_idx, &needle);
+}
+
+static WatcherDataIPAddr *
+_watcher_ip_data_lookup_addr(NMNetns *self, const NMIPAddrTyped *addr)
+{
+    return _watcher_ip_data_lookup(self, addr->addr_family, &addr->addr);
+}
+
+static guint
+_watcher_ip_data_hash(gconstpointer _data)
+{
+    const WatcherDataIPAddr *data = _data;
+    NMHashState              h;
+
+    nm_assert(data);
+
+    nm_hash_init(&h, 3152126191u);
+    nm_ip_addr_typed_hash_update(&h, &data->addr);
+    return nm_hash_complete(&h);
+}
+
+static gboolean
+_watcher_ip_data_equal(gconstpointer a, gconstpointer b)
+{
+    const WatcherDataIPAddr *data_a = a;
+    const WatcherDataIPAddr *data_b = b;
+
+    nm_assert(data_a);
+    nm_assert(data_b);
+
+    return nm_ip_addr_typed_equal(&data_a->addr, &data_b->addr);
+}
+
+static NMNetnsWatcherHandle *
+_watcher_lookup_handle(NMNetns                  *self,
+                       NMNetnsWatcherType        watcher_type,
+                       const NMNetnsWatcherData *watcher_data,
+                       gconstpointer             tag)
+{
+    NMNetnsWatcherHandle handle_needle;
+
+    nm_assert(NM_IS_NETNS(self));
+    nm_assert(tag);
+
+    _watcher_handle_init(&handle_needle, watcher_type, watcher_data, tag);
+    return g_hash_table_lookup(NM_NETNS_GET_PRIVATE(self)->watcher_idx, &handle_needle);
+}
+
+static void
+_watcher_register_handle(NMNetns *self, NMNetnsWatcherHandle *handle)
+{
+    NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
+
+    switch (handle->watcher_type) {
+    case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+    {
+        WatcherDataIPAddr *data;
+
+        data = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr);
+        if (!data) {
+            data  = g_slice_new(WatcherDataIPAddr);
+            *data = (WatcherDataIPAddr){
+                .addr                     = handle->watcher_data.ip_addr.addr,
+                .watcher_ip_addr_lst_head = C_LIST_INIT(data->watcher_ip_addr_lst_head),
+            };
+            if (!g_hash_table_add(priv->watcher_ip_data_idx, data))
+                nm_assert_not_reached();
+        }
+
+        c_list_link_tail(&data->watcher_ip_addr_lst_head,
+                         &handle->reg_data.ip_addr.watcher_ip_addr_lst);
+        return;
+    }
+    }
+    nm_assert_not_reached();
+}
+
+static void
+_watcher_unregister_handle(NMNetns *self, NMNetnsWatcherHandle *handle)
+{
+    NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
+
+    switch (handle->watcher_type) {
+    case NM_NETNS_WATCHER_TYPE_IP_ADDR:
+    {
+        gboolean is_last;
+
+        nm_assert(({
+            WatcherDataIPAddr *d;
+
+            d = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr);
+            d &&c_list_contains(&d->watcher_ip_addr_lst_head,
+                                &handle->reg_data.ip_addr.watcher_ip_addr_lst);
+        }));
+
+        is_last = c_list_is_empty_or_single(&handle->reg_data.ip_addr.watcher_ip_addr_lst);
+
+        c_list_unlink(&handle->reg_data.ip_addr.watcher_ip_addr_lst);
+
+        if (is_last) {
+            WatcherDataIPAddr *data;
+
+            data = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr);
+            nm_assert(data);
+            nm_assert(c_list_is_empty(&data->watcher_ip_addr_lst_head));
+
+            if (!g_hash_table_remove(priv->watcher_ip_data_idx, data))
+                nm_assert_not_reached();
+
+            nm_g_slice_free(data);
+        }
+        return;
+    }
+    }
+    nm_assert_not_reached();
+}
+
+void
+nm_netns_watcher_add(NMNetns                  *self,
+                     NMNetnsWatcherType        watcher_type,
+                     const NMNetnsWatcherData *watcher_data,
+                     gconstpointer             tag,
+                     NMNetnsWatcherCallback    callback,
+                     gpointer                  user_data)
+{
+    NMNetnsPrivate       *priv;
+    NMNetnsWatcherHandle *handle;
+    gboolean              is_new = FALSE;
+    char                  sbuf[500];
+
+    g_return_if_fail(NM_IS_NETNS(self));
+    g_return_if_fail(NM_NETNS_WATCHER_TYPE_VALID(watcher_type));
+    g_return_if_fail(callback);
+    g_return_if_fail(tag);
+
+    priv = NM_NETNS_GET_PRIVATE(self);
+
+    handle = _watcher_lookup_handle(self, watcher_type, watcher_data, tag);
+
+    if (!handle) {
+        WatcherByTag *watcher_by_tag;
+
+        if (G_UNLIKELY(g_hash_table_size(priv->watcher_idx) == 0))
+            g_object_ref(self);
+
+        handle = g_slice_new(NMNetnsWatcherHandle);
+        _watcher_handle_init(handle, watcher_type, watcher_data, tag);
+
+        if (!g_hash_table_add(priv->watcher_idx, handle))
+            nm_assert_not_reached();
+
+        watcher_by_tag = g_hash_table_lookup(priv->watcher_by_tag_idx, &tag);
+
+        if (!watcher_by_tag) {
+            watcher_by_tag  = g_slice_new(WatcherByTag);
+            *watcher_by_tag = (WatcherByTag){
+                .tag                     = tag,
+                .watcher_by_tag_lst_head = C_LIST_INIT(watcher_by_tag->watcher_by_tag_lst_head),
+            };
+            g_hash_table_add(priv->watcher_by_tag_idx, watcher_by_tag);
+        }
+
+        c_list_link_tail(&watcher_by_tag->watcher_by_tag_lst_head, &handle->watcher_tag_lst);
+
+        is_new = TRUE;
+    } else {
+        /* Handles are deduplicated/shared. Hence it is error prone (and likely
+         * a bug) to provide different callback/user_data. Such usage is
+         * rejected here.
+         *
+         * This could be made to work, for example by now allowing handles to
+         * be merged or simply requiring the caller to be careful to not get
+         * this wrong. But that is currently not implemented nor needed.
+         */
+        nm_assert(!tag
+                  || (handle->callback == callback && handle->callback_user_data == user_data));
+    }
+
+    if (_LOGT_ENABLED()
+        && (is_new || handle->callback != callback || handle->callback_user_data != user_data)) {
+        _LOGT("netns-watcher: %s %s",
+              is_new ? "register" : "update",
+              _watcher_handle_to_string(handle, sbuf, sizeof(sbuf)));
+    }
+
+    handle->callback           = callback;
+    handle->callback_user_data = user_data;
+    handle->watcher_dirty      = FALSE;
+
+    if (is_new)
+        _watcher_register_handle(self, handle);
+
+    /* We cannot return a handle here, because handles are deduplicated via the priv->watchers_idx dictionary.
+     * The usage pattern is to use nm_netns_watcher_remove_all(), and not remove them one by one.
+     * As nm_netns_watcher_add() can return the same handle more than once, the user
+     * wouldn't know when it's safe to call nm_netns_watcher_remove_handle().
+     *
+     * This could be extended by adding a ref-count to the handles. But that is not
+     * used currently, so it's not possible to remove watcher by their handle. */
+}
+
+static void
+nm_netns_watcher_remove_handle(NMNetns *self, NMNetnsWatcherHandle *handle)
+{
+    NMNetnsPrivate *priv;
+    char            sbuf[500];
+
+    g_return_if_fail(NM_IS_NETNS(self));
+    g_return_if_fail(handle);
+    nm_assert(handle->tag);
+
+    priv = NM_NETNS_GET_PRIVATE(self);
+
+    nm_assert(g_hash_table_lookup(priv->watcher_idx, handle) == handle);
+
+    _LOGT("netns-watcher: %s %s",
+          "unregister",
+          _watcher_handle_to_string(handle, sbuf, sizeof(sbuf)));
+
+    _watcher_unregister_handle(self, handle);
+
+    if (!g_hash_table_remove(priv->watcher_idx, handle))
+        nm_assert_not_reached();
+
+    if (c_list_is_empty_or_single(&handle->watcher_tag_lst)) {
+        if (!g_hash_table_remove(priv->watcher_by_tag_idx, &handle->tag))
+            nm_assert_not_reached();
+    }
+
+    c_list_unlink_stale(&handle->watcher_tag_lst);
+    nm_g_slice_free(handle);
+
+    if (G_UNLIKELY(g_hash_table_size(priv->watcher_idx) == 0))
+        g_object_unref(self);
+}
+
+void
+nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all)
+{
+    NMNetnsPrivate       *priv;
+    WatcherByTag         *watcher_by_tag;
+    NMNetnsWatcherHandle *handle;
+    NMNetnsWatcherHandle *handle_safe;
+
+    g_return_if_fail(NM_IS_NETNS(self));
+
+    /* remove-all only works with handles that have a tag associated.
+     * Since NMNetns can have multiple users that are unknown to each
+     * other, it makes no sense to have a remove-all function which
+     * would remove all of them. */
+    g_return_if_fail(tag);
+
+    priv = NM_NETNS_GET_PRIVATE(self);
+
+    watcher_by_tag = g_hash_table_lookup(priv->watcher_by_tag_idx, &tag);
+    if (!watcher_by_tag)
+        return;
+
+    c_list_for_each_entry_safe (handle,
+                                handle_safe,
+                                &watcher_by_tag->watcher_by_tag_lst_head,
+                                watcher_tag_lst) {
+        gboolean is_last;
+
+        if (!all && !handle->watcher_dirty) {
+            /* Survivors are marked as dirty. This enables a pattern where you
+             * call nm_netns_watcher_add() on the elements you care about
+             * (which clears the dirty flag), and then remove all dirty ones
+             * with nm_netns_watcher_remove_all() (which marks the remaining
+             * handles as dirty for the next time). */
+            handle->watcher_dirty = TRUE;
+            continue;
+        }
+
+        is_last = c_list_is_empty_or_single(&watcher_by_tag->watcher_by_tag_lst_head);
+        nm_netns_watcher_remove_handle(self, handle);
+
+        if (is_last) {
+            /* Removing the last handle destroys the "watcher_by_tag" and may even
+             * destroy "self". We must not touch those pointers hereafter.
+             *
+             * If you ever *not* return here, make sure to handle that! */
+            return;
         }
     }
 }
@@ -850,6 +1421,7 @@ nm_netns_init(NMNetns *self)
     NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self);
 
     priv->_self_signal_user_data = self;
+
     c_list_init(&priv->l3cfg_signal_pending_lst_head);
 
     G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(EcmpTrackObj, obj) == 0);
@@ -859,6 +1431,14 @@ nm_netns_init(NMNetns *self)
                                                        _ecmp_routes_by_ecmpid_equal,
                                                        _ecmp_routes_by_ecmpid_free,
                                                        NULL);
+
+    priv->watcher_idx = g_hash_table_new(_watcher_handle_hash, _watcher_handle_equal);
+    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(WatcherByTag, tag) == 0);
+    priv->watcher_by_tag_idx  = g_hash_table_new_full(nm_pdirect_hash,
+                                                     nm_pdirect_equal,
+                                                     (GDestroyNotify) _watcher_by_tag_destroy,
+                                                     NULL);
+    priv->watcher_ip_data_idx = g_hash_table_new(_watcher_ip_data_hash, _watcher_ip_data_equal);
 }
 
 static void
@@ -937,10 +1517,17 @@ dispose(GObject *object)
     nm_assert(nm_g_hash_table_size(priv->l3cfgs) == 0);
     nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head));
     nm_assert(!priv->shared_ips);
+    nm_assert(nm_g_hash_table_size(priv->watcher_idx) == 0);
+    nm_assert(nm_g_hash_table_size(priv->watcher_by_tag_idx) == 0);
+    nm_assert(nm_g_hash_table_size(priv->watcher_ip_data_idx) == 0);
 
     nm_clear_pointer(&priv->ecmp_track_by_obj, g_hash_table_destroy);
     nm_clear_pointer(&priv->ecmp_track_by_ecmpid, g_hash_table_destroy);
 
+    nm_clear_pointer(&priv->watcher_idx, g_hash_table_destroy);
+    nm_clear_pointer(&priv->watcher_by_tag_idx, g_hash_table_destroy);
+    nm_clear_pointer(&priv->watcher_ip_data_idx, g_hash_table_destroy);
+
     nm_clear_g_source_inst(&priv->signal_pending_idle_source);
 
     if (priv->platform)
diff --git a/src/core/nm-netns.h b/src/core/nm-netns.h
index 84a78f83..7725ae79 100644
--- a/src/core/nm-netns.h
+++ b/src/core/nm-netns.h
@@ -59,4 +59,46 @@ void nm_netns_ip_route_ecmp_commit(NMNetns    *self,
                                    GPtrArray **routes,
                                    gboolean    is_reapply);
 
+/*****************************************************************************/
+
+typedef enum {
+    NM_NETNS_WATCHER_TYPE_IP_ADDR,
+} NMNetnsWatcherType;
+
+typedef struct {
+    union {
+        struct {
+            NMIPAddrTyped addr;
+        } ip_addr;
+    };
+} NMNetnsWatcherData;
+
+typedef struct {
+    union {
+        struct {
+            const NMPObject           *obj;
+            NMPlatformSignalChangeType change_type;
+        } ip_addr;
+    };
+} NMNetnsWatcherEventData;
+
+typedef struct _NMNetnsWatcherHandle NMNetnsWatcherHandle;
+
+typedef void (*NMNetnsWatcherCallback)(NMNetns                       *self,
+                                       NMNetnsWatcherType             watcher_type,
+                                       const NMNetnsWatcherData      *watcher_data,
+                                       gconstpointer                  tag,
+                                       const NMNetnsWatcherEventData *event_data,
+                                       gpointer                       user_data);
+
+void nm_netns_watcher_add(NMNetns                  *self,
+                          NMNetnsWatcherType        watcher_type,
+                          const NMNetnsWatcherData *watcher_data,
+                          gconstpointer             tag,
+                          NMNetnsWatcherCallback    callback,
+                          gpointer                  user_data);
+
+void
+nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all /* or only dirty */);
+
 #endif /* __NM_NETNS_H__ */
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
index d7e05b7b..efdb0636 100644
--- a/src/core/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -51,7 +51,7 @@ typedef struct {
     NMManager          *manager;
     NMNetns            *netns;
     NMFirewalldManager *firewalld_manager;
-    CList               pending_activation_checks;
+    CList               policy_auto_activate_lst_head;
 
     NMAgentManager *agent_mgr;
 
@@ -62,6 +62,10 @@ typedef struct {
 
     NMSettings *settings;
 
+    GSource *device_recheck_auto_activate_all_idle_source;
+
+    GSource *reset_connections_retries_idle_source;
+
     NMHostnameManager *hostname_manager;
 
     NMActiveConnection *default_ac4, *activating_ac4;
@@ -70,10 +74,6 @@ typedef struct {
     NMDnsManager *dns_manager;
     gulong        config_changed_id;
 
-    guint reset_retries_id; /* idle handler for resetting the retries count */
-
-    guint schedule_activate_all_id; /* idle handler for schedule_activate_all(). */
-
     NMPolicyHostnameMode hostname_mode;
     char                *orig_hostname;     /* hostname at NM start time */
     char                *cur_hostname;      /* hostname we want to assign */
@@ -135,8 +135,7 @@ _PRIV_TO_SELF(NMPolicyPrivate *priv)
 /*****************************************************************************/
 
 static void      update_system_hostname(NMPolicy *self, const char *msg);
-static void      schedule_activate_all(NMPolicy *self);
-static void      schedule_activate_check(NMPolicy *self, NMDevice *device);
+static void      nm_policy_device_recheck_auto_activate_all_schedule(NMPolicy *self);
 static NMDevice *get_default_device(NMPolicy *self, int addr_family);
 
 /*****************************************************************************/
@@ -1283,23 +1282,6 @@ check_activating_active_connections(NMPolicy *self)
     g_object_thaw_notify(G_OBJECT(self));
 }
 
-typedef struct {
-    CList     pending_lst;
-    NMPolicy *policy;
-    NMDevice *device;
-    guint     autoactivate_id;
-} ActivateData;
-
-static void
-activate_data_free(ActivateData *data)
-{
-    nm_device_remove_pending_action(data->device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE);
-    c_list_unlink_stale(&data->pending_lst);
-    nm_clear_g_source(&data->autoactivate_id);
-    g_object_unref(data->device);
-    g_slice_free(ActivateData, data);
-}
-
 static void
 pending_ac_gone(gpointer data, GObject *where_the_object_was)
 {
@@ -1326,13 +1308,17 @@ pending_ac_state_changed(NMActiveConnection *ac, guint state, guint reason, NMPo
          * device, but block the current connection to avoid an activation
          * loop.
          */
-        if (reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED) {
+        if (reason != NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED
+            && reason != NM_ACTIVE_CONNECTION_STATE_REASON_CONNECTION_REMOVED) {
             con = nm_active_connection_get_settings_connection(ac);
-            nm_settings_connection_autoconnect_blocked_reason_set(
+            nm_manager_devcon_autoconnect_blocked_reason_set(
+                priv->manager,
+                nm_active_connection_get_device(ac),
                 con,
-                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
                 TRUE);
-            schedule_activate_check(self, nm_active_connection_get_device(ac));
+            nm_policy_device_recheck_auto_activate_schedule(self,
+                                                            nm_active_connection_get_device(ac));
         }
 
         /* Cleanup */
@@ -1345,7 +1331,7 @@ pending_ac_state_changed(NMActiveConnection *ac, guint state, guint reason, NMPo
 }
 
 static void
-auto_activate_device(NMPolicy *self, NMDevice *device)
+_auto_activate_device(NMPolicy *self, NMDevice *device)
 {
     NMPolicyPrivate               *priv;
     NMSettingsConnection          *best_connection;
@@ -1391,7 +1377,7 @@ auto_activate_device(NMPolicy *self, NMDevice *device)
         NMSettingConnection  *s_con;
         const char           *permission;
 
-        if (nm_settings_connection_autoconnect_is_blocked(candidate))
+        if (nm_manager_devcon_autoconnect_is_blocked(priv->manager, device, candidate))
             continue;
 
         cand_conn = nm_settings_connection_get_connection(candidate);
@@ -1435,11 +1421,13 @@ auto_activate_device(NMPolicy *self, NMDevice *device)
               "connection '%s' auto-activation failed: %s",
               nm_settings_connection_get_id(best_connection),
               error->message);
-        nm_settings_connection_autoconnect_blocked_reason_set(
+        nm_manager_devcon_autoconnect_blocked_reason_set(
+            priv->manager,
+            device,
             best_connection,
-            NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+            NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
             TRUE);
-        schedule_activate_check(self, device);
+        nm_policy_device_recheck_auto_activate_schedule(self, device);
         return;
     }
 
@@ -1456,32 +1444,33 @@ auto_activate_device(NMPolicy *self, NMDevice *device)
     }
 }
 
-static gboolean
-auto_activate_device_cb(gpointer user_data)
+static void
+_auto_activate_device_clear(NMPolicy *self, NMDevice *device, gboolean do_activate)
 {
-    ActivateData *data = user_data;
+    nm_assert(NM_IS_DEVICE(device));
+    nm_assert(NM_IS_POLICY(self));
+    nm_assert(c_list_is_linked(&device->policy_auto_activate_lst));
+    nm_assert(c_list_contains(&NM_POLICY_GET_PRIVATE(self)->policy_auto_activate_lst_head,
+                              &device->policy_auto_activate_lst));
 
-    g_assert(data);
-    g_assert(NM_IS_POLICY(data->policy));
-    g_assert(NM_IS_DEVICE(data->device));
+    c_list_unlink(&device->policy_auto_activate_lst);
+    nm_clear_g_source_inst(&device->policy_auto_activate_idle_source);
 
-    data->autoactivate_id = 0;
-    auto_activate_device(data->policy, data->device);
-    activate_data_free(data);
-    return G_SOURCE_REMOVE;
+    if (do_activate)
+        _auto_activate_device(self, device);
+
+    nm_device_remove_pending_action(device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE);
 }
 
-static ActivateData *
-find_pending_activation(NMPolicy *self, NMDevice *device)
+static gboolean
+_auto_activate_idle_cb(gpointer user_data)
 {
-    NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
-    ActivateData    *data;
+    NMDevice *device = user_data;
 
-    c_list_for_each_entry (data, &priv->pending_activation_checks, pending_lst) {
-        if (data->device == device)
-            return data;
-    }
-    return NULL;
+    nm_assert(NM_IS_DEVICE(device));
+
+    _auto_activate_device_clear(nm_manager_get_policy(nm_device_get_manager(device)), device, TRUE);
+    return G_SOURCE_CONTINUE;
 }
 
 /*****************************************************************************/
@@ -1600,10 +1589,12 @@ nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self)
         NMConnection         *connection = nm_settings_connection_get_connection(sett_conn);
 
         if (nm_connection_get_setting_ovs_interface(connection)) {
-            nm_settings_connection_autoconnect_retries_reset(sett_conn);
-            nm_settings_connection_autoconnect_blocked_reason_set(
+            nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn);
+            nm_manager_devcon_autoconnect_blocked_reason_set(
+                priv->manager,
+                NULL,
                 sett_conn,
-                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
                 FALSE);
         }
     }
@@ -1634,36 +1625,15 @@ reset_autoconnect_all(
             && !nm_device_check_connection_compatible(
                 device,
                 nm_settings_connection_get_connection(sett_conn),
+                TRUE,
                 NULL))
             continue;
 
-        if (only_no_secrets) {
-            /* we only reset the no-secrets blocked flag. */
-            if (nm_settings_connection_autoconnect_blocked_reason_set(
-                    sett_conn,
-                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS,
-                    FALSE)) {
-                /* maybe the connection is still blocked afterwards for other reasons
-                 * and in the larger picture nothing changed. But it's too complicated
-                 * to find out exactly. Just assume, something changed to be sure. */
-                if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
-                    changed = TRUE;
-            }
-        } else {
-            /* we reset the tries-count and any blocked-reason */
-            if (nm_settings_connection_autoconnect_retries_get(sett_conn) == 0)
-                changed = TRUE;
-            nm_settings_connection_autoconnect_retries_reset(sett_conn);
-
-            if (nm_settings_connection_autoconnect_blocked_reason_set(
-                    sett_conn,
-                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL
-                        & ~NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
-                    FALSE)) {
-                if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
-                    changed = TRUE;
-            }
-        }
+        if (nm_manager_devcon_autoconnect_reset_reconnect_all(priv->manager,
+                                                              device,
+                                                              sett_conn,
+                                                              only_no_secrets))
+            changed = TRUE;
     }
     return changed;
 }
@@ -1683,21 +1653,35 @@ sleeping_changed(NMManager *manager, GParamSpec *pspec, gpointer user_data)
         reset_autoconnect_all(self, NULL, FALSE);
 }
 
-static void
-schedule_activate_check(NMPolicy *self, NMDevice *device)
+void
+nm_policy_device_recheck_auto_activate_schedule(NMPolicy *self, NMDevice *device)
 {
-    NMPolicyPrivate    *priv = NM_POLICY_GET_PRIVATE(self);
-    ActivateData       *data;
+    NMPolicyPrivate    *priv;
     NMActiveConnection *ac;
     const CList        *tmp_list;
 
-    if (nm_manager_get_state(priv->manager) == NM_STATE_ASLEEP)
+    g_return_if_fail(NM_IS_POLICY(self));
+    g_return_if_fail(NM_IS_DEVICE(device));
+    nm_assert(g_signal_handler_find(device,
+                                    G_SIGNAL_MATCH_DATA,
+                                    0,
+                                    0,
+                                    NULL,
+                                    NULL,
+                                    NM_POLICY_GET_PRIVATE(self))
+              != 0);
+
+    if (!c_list_is_empty(&device->policy_auto_activate_lst)) {
+        /* already queued. Return. */
         return;
+    }
 
-    if (!nm_device_autoconnect_allowed(device))
+    priv = NM_POLICY_GET_PRIVATE(self);
+
+    if (nm_manager_get_state(priv->manager) == NM_STATE_ASLEEP)
         return;
 
-    if (find_pending_activation(self, device))
+    if (!nm_device_autoconnect_allowed(device))
         return;
 
     nm_manager_for_each_active_connection (priv->manager, ac, tmp_list) {
@@ -1712,11 +1696,8 @@ schedule_activate_check(NMPolicy *self, NMDevice *device)
 
     nm_device_add_pending_action(device, NM_PENDING_ACTION_AUTOACTIVATE, TRUE);
 
-    data                  = g_slice_new0(ActivateData);
-    data->policy          = self;
-    data->device          = g_object_ref(device);
-    data->autoactivate_id = g_idle_add(auto_activate_device_cb, data);
-    c_list_link_tail(&priv->pending_activation_checks, &data->pending_lst);
+    c_list_link_tail(&priv->policy_auto_activate_lst_head, &device->policy_auto_activate_lst);
+    device->policy_auto_activate_idle_source = nm_g_idle_add_source(_auto_activate_idle_cb, device);
 }
 
 static gboolean
@@ -1729,7 +1710,7 @@ reset_connections_retries(gpointer user_data)
     gint32                       con_stamp, min_stamp, now;
     gboolean                     changed = FALSE;
 
-    priv->reset_retries_id = 0;
+    nm_clear_g_source_inst(&priv->reset_connections_retries_idle_source);
 
     min_stamp   = 0;
     now         = nm_utils_get_monotonic_timestamp_sec();
@@ -1737,91 +1718,83 @@ reset_connections_retries(gpointer user_data)
     for (i = 0; connections[i]; i++) {
         NMSettingsConnection *connection = connections[i];
 
-        con_stamp = nm_settings_connection_autoconnect_retries_blocked_until(connection);
+        con_stamp =
+            nm_manager_devcon_autoconnect_retries_blocked_until(priv->manager, NULL, connection);
         if (con_stamp == 0)
             continue;
 
         if (con_stamp <= now) {
-            nm_settings_connection_autoconnect_retries_reset(connection);
+            nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, connection);
             changed = TRUE;
         } else if (min_stamp == 0 || min_stamp > con_stamp)
             min_stamp = con_stamp;
     }
 
     /* Schedule the handler again if there are some stamps left */
-    if (min_stamp != 0)
-        priv->reset_retries_id =
-            g_timeout_add_seconds(min_stamp - now, reset_connections_retries, self);
+    if (min_stamp != 0) {
+        priv->reset_connections_retries_idle_source =
+            nm_g_timeout_add_seconds_source(min_stamp - now, reset_connections_retries, self);
+    }
 
     /* If anything changed, try to activate the newly re-enabled connections */
     if (changed)
-        schedule_activate_all(self);
+        nm_policy_device_recheck_auto_activate_all_schedule(self);
 
-    return FALSE;
+    return G_SOURCE_CONTINUE;
 }
 
 static void
-_connection_autoconnect_retries_set(NMPolicy *self, NMSettingsConnection *connection, int tries)
+_connection_autoconnect_retries_set(NMPolicy             *self,
+                                    NMDevice             *device,
+                                    NMSettingsConnection *connection,
+                                    guint32               tries)
 {
     NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
 
     nm_assert(NM_IS_SETTINGS_CONNECTION(connection));
-    nm_assert(tries >= 0);
 
-    nm_settings_connection_autoconnect_retries_set(connection, tries);
+    nm_manager_devcon_autoconnect_retries_set(priv->manager, device, connection, tries);
 
     if (tries == 0) {
         /* Schedule a handler to reset retries count */
-        if (!priv->reset_retries_id) {
-            gint32 retry_time =
-                nm_settings_connection_autoconnect_retries_blocked_until(connection);
-
-            g_warn_if_fail(retry_time != 0);
-            priv->reset_retries_id =
-                g_timeout_add_seconds(MAX(0, retry_time - nm_utils_get_monotonic_timestamp_sec()),
-                                      reset_connections_retries,
-                                      self);
+        if (!priv->reset_connections_retries_idle_source) {
+            gint32 retry_time;
+
+            retry_time = nm_manager_devcon_autoconnect_retries_blocked_until(priv->manager,
+                                                                             device,
+                                                                             connection);
+            nm_assert(retry_time != 0);
+
+            priv->reset_connections_retries_idle_source = nm_g_timeout_add_seconds_source(
+                MAX(0, retry_time - nm_utils_get_monotonic_timestamp_sec()),
+                reset_connections_retries,
+                self);
         }
     }
 }
 
 static void
-activate_slave_connections(NMPolicy *self, NMDevice *device)
+unblock_autoconnect_for_ports(NMPolicy   *self,
+                              const char *master_device,
+                              const char *master_uuid_settings,
+                              const char *master_uuid_applied,
+                              gboolean    reset_devcon_autoconnect)
 {
     NMPolicyPrivate             *priv = NM_POLICY_GET_PRIVATE(self);
-    const char                  *master_device;
-    const char                  *master_uuid_settings = NULL;
-    const char                  *master_uuid_applied  = NULL;
-    guint                        i;
-    NMActRequest                *req;
-    gboolean                     internal_activation = FALSE;
     NMSettingsConnection *const *connections;
     gboolean                     changed;
+    guint                        i;
 
-    master_device = nm_device_get_iface(device);
-    g_assert(master_device);
-
-    req = nm_device_get_act_request(device);
-    if (req) {
-        NMConnection         *connection;
-        NMSettingsConnection *sett_conn;
-        NMAuthSubject        *subject;
-
-        connection = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req));
-        if (connection)
-            master_uuid_applied = nm_connection_get_uuid(connection);
-
-        sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req));
-        if (sett_conn) {
-            master_uuid_settings = nm_settings_connection_get_uuid(sett_conn);
-            if (nm_streq0(master_uuid_settings, master_uuid_applied))
-                master_uuid_settings = NULL;
-        }
-
-        subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req));
-        internal_activation =
-            subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL);
-    }
+    _LOGT(LOGD_CORE,
+          "block-autoconnect: unblocking port profiles for controller ifname=%s%s%s, uuid=%s%s%s"
+          "%s%s%s",
+          NM_PRINT_FMT_QUOTE_STRING(master_device),
+          NM_PRINT_FMT_QUOTE_STRING(master_uuid_settings),
+          NM_PRINT_FMT_QUOTED(master_uuid_applied,
+                              ", applied-uuid=\"",
+                              master_uuid_applied,
+                              "\"",
+                              ""));
 
     changed     = FALSE;
     connections = nm_settings_get_connections(priv->settings, NULL);
@@ -1831,21 +1804,25 @@ activate_slave_connections(NMPolicy *self, NMDevice *device)
         const char           *slave_master;
 
         s_slave_con =
-            nm_connection_get_setting_connection(nm_settings_connection_get_connection(sett_conn));
+            nm_settings_connection_get_setting(sett_conn, NM_META_SETTING_TYPE_CONNECTION);
         slave_master = nm_setting_connection_get_master(s_slave_con);
         if (!slave_master)
             continue;
+
         if (!NM_IN_STRSET(slave_master, master_device, master_uuid_applied, master_uuid_settings))
             continue;
 
-        if (!internal_activation) {
-            if (nm_settings_connection_autoconnect_retries_get(sett_conn) == 0)
+        if (reset_devcon_autoconnect) {
+            if (nm_manager_devcon_autoconnect_retries_reset(priv->manager, NULL, sett_conn))
                 changed = TRUE;
-            nm_settings_connection_autoconnect_retries_reset(sett_conn);
         }
-        if (nm_settings_connection_autoconnect_blocked_reason_set(
+
+        /* unblock the devices associated with that connection */
+        if (nm_manager_devcon_autoconnect_blocked_reason_set(
+                priv->manager,
+                NULL,
                 sett_conn,
-                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
                 FALSE)) {
             if (!nm_settings_connection_autoconnect_is_blocked(sett_conn))
                 changed = TRUE;
@@ -1853,7 +1830,68 @@ activate_slave_connections(NMPolicy *self, NMDevice *device)
     }
 
     if (changed)
-        schedule_activate_all(self);
+        nm_policy_device_recheck_auto_activate_all_schedule(self);
+}
+
+static void
+unblock_autoconnect_for_ports_for_sett_conn(NMPolicy *self, NMSettingsConnection *sett_conn)
+{
+    const char          *master_device;
+    const char          *master_uuid_settings;
+    NMSettingConnection *s_con;
+
+    nm_assert(NM_IS_POLICY(self));
+    nm_assert(NM_IS_SETTINGS_CONNECTION(sett_conn));
+
+    s_con = nm_settings_connection_get_setting(sett_conn, NM_META_SETTING_TYPE_CONNECTION);
+
+    nm_assert(NM_IS_SETTING_CONNECTION(s_con));
+
+    master_uuid_settings = nm_setting_connection_get_uuid(s_con);
+    master_device        = nm_setting_connection_get_interface_name(s_con);
+
+    unblock_autoconnect_for_ports(self, master_device, master_uuid_settings, NULL, TRUE);
+}
+
+static void
+activate_slave_connections(NMPolicy *self, NMDevice *device)
+{
+    const char   *master_device;
+    const char   *master_uuid_settings = NULL;
+    const char   *master_uuid_applied  = NULL;
+    NMActRequest *req;
+    gboolean      internal_activation = FALSE;
+
+    master_device = nm_device_get_iface(device);
+    nm_assert(master_device);
+
+    req = nm_device_get_act_request(device);
+    if (req) {
+        NMConnection         *connection;
+        NMSettingsConnection *sett_conn;
+        NMAuthSubject        *subject;
+
+        sett_conn = nm_active_connection_get_settings_connection(NM_ACTIVE_CONNECTION(req));
+        if (sett_conn)
+            master_uuid_settings = nm_settings_connection_get_uuid(sett_conn);
+
+        connection = nm_active_connection_get_applied_connection(NM_ACTIVE_CONNECTION(req));
+        if (connection)
+            master_uuid_applied = nm_connection_get_uuid(connection);
+
+        if (nm_streq0(master_uuid_settings, master_uuid_applied))
+            master_uuid_applied = NULL;
+
+        subject = nm_active_connection_get_subject(NM_ACTIVE_CONNECTION(req));
+        internal_activation =
+            subject && (nm_auth_subject_get_subject_type(subject) == NM_AUTH_SUBJECT_TYPE_INTERNAL);
+    }
+
+    unblock_autoconnect_for_ports(self,
+                                  master_device,
+                                  master_uuid_settings,
+                                  master_uuid_applied,
+                                  !internal_activation);
 }
 
 static gboolean
@@ -1968,9 +2006,11 @@ device_state_changed(NMDevice           *device,
          * a missing SIM or wrong modem initialization).
          */
         if (sett_conn) {
-            nm_settings_connection_autoconnect_blocked_reason_set(
+            nm_manager_devcon_autoconnect_blocked_reason_set(
+                priv->manager,
+                device,
                 sett_conn,
-                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
                 TRUE);
         }
         break;
@@ -1988,10 +2028,10 @@ device_state_changed(NMDevice           *device,
         if (sett_conn && old_state >= NM_DEVICE_STATE_PREPARE
             && old_state <= NM_DEVICE_STATE_ACTIVATED) {
             gboolean blocked = FALSE;
-            int      tries;
             guint64  con_v;
 
-            if (nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_NO_SECRETS) {
+            switch (nm_device_state_reason_check(reason)) {
+            case NM_DEVICE_STATE_REASON_NO_SECRETS:
                 /* we want to block the connection from auto-connect if it failed due to no-secrets.
                  * However, if a secret-agent registered, since the connection made the last
                  * secret-request, we do not block it. The new secret-agent might not yet
@@ -2008,16 +2048,17 @@ device_state_changed(NMDevice           *device,
                 con_v = nm_settings_connection_get_last_secret_agent_version_id(sett_conn);
                 if (con_v == 0 || con_v == nm_agent_manager_get_agent_version_id(priv->agent_mgr)) {
                     _LOGD(LOGD_DEVICE,
-                          "connection '%s' now blocked from autoconnect due to no secrets",
+                          "block-autoconnect: connection '%s' now blocked from autoconnect due to "
+                          "no secrets",
                           nm_settings_connection_get_id(sett_conn));
                     nm_settings_connection_autoconnect_blocked_reason_set(
                         sett_conn,
-                        NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS,
+                        NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS,
                         TRUE);
                     blocked = TRUE;
                 }
-            } else if (nm_device_state_reason_check(reason)
-                       == NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED) {
+                break;
+            case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
                 /* A connection that fails due to dependency-failed is not
                  * able to reconnect until the master connection activates
                  * again; when this happens, the master clears the blocked
@@ -2027,26 +2068,41 @@ device_state_changed(NMDevice           *device,
                  * dependency-failed.
                  */
                 _LOGD(LOGD_DEVICE,
-                      "connection '%s' now blocked from autoconnect due to failed dependency",
+                      "block-autoconnect: connection[%p] (%s) now blocked from autoconnect due to "
+                      "failed "
+                      "dependency",
+                      sett_conn,
                       nm_settings_connection_get_id(sett_conn));
-                nm_settings_connection_autoconnect_blocked_reason_set(
+                nm_manager_devcon_autoconnect_blocked_reason_set(
+                    priv->manager,
+                    device,
                     sett_conn,
-                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED,
+                    NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
                     TRUE);
                 blocked = TRUE;
+                break;
+            default:
+                break;
             }
 
             if (!blocked) {
-                tries = nm_settings_connection_autoconnect_retries_get(sett_conn);
-                if (tries > 0) {
+                guint32 tries;
+
+                tries = nm_manager_devcon_autoconnect_retries_get(priv->manager, device, sett_conn);
+                if (tries == 0) {
+                    /* blocked */
+                } else if (tries != NM_AUTOCONNECT_RETRIES_FOREVER) {
                     _LOGD(LOGD_DEVICE,
-                          "connection '%s' failed to autoconnect; %d tries left",
+                          "autoconnect: connection[%p] (%s): failed to autoconnect; %u tries left",
+                          sett_conn,
                           nm_settings_connection_get_id(sett_conn),
-                          tries - 1);
-                    _connection_autoconnect_retries_set(self, sett_conn, tries - 1);
-                } else if (tries != 0) {
+                          tries - 1u);
+                    _connection_autoconnect_retries_set(self, device, sett_conn, tries - 1u);
+                } else {
                     _LOGD(LOGD_DEVICE,
-                          "connection '%s' failed to autoconnect; infinite tries left",
+                          "autoconnect: connection[%p] (%s) failed to autoconnect; infinite tries "
+                          "left",
+                          sett_conn,
                           nm_settings_connection_get_id(sett_conn));
                 }
             }
@@ -2055,7 +2111,7 @@ device_state_changed(NMDevice           *device,
     case NM_DEVICE_STATE_ACTIVATED:
         if (sett_conn) {
             /* Reset auto retries back to default since connection was successful */
-            nm_settings_connection_autoconnect_retries_reset(sett_conn);
+            nm_manager_devcon_autoconnect_retries_reset(priv->manager, device, sett_conn);
         }
 
         /* Since there is no guarantee that device_l3cd_changed() is called
@@ -2087,27 +2143,34 @@ device_state_changed(NMDevice           *device,
     case NM_DEVICE_STATE_DEACTIVATING:
         if (sett_conn) {
             NMSettingsAutoconnectBlockedReason blocked_reason =
-                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE;
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE;
 
             switch (nm_device_state_reason_check(reason)) {
             case NM_DEVICE_STATE_REASON_USER_REQUESTED:
-                blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST;
+                blocked_reason = NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST;
                 break;
             case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
-                blocked_reason = NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED;
+                blocked_reason = NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED;
                 break;
             default:
                 break;
             }
-            if (blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE) {
+            if (blocked_reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE) {
                 _LOGD(LOGD_DEVICE,
-                      "blocking autoconnect of connection '%s': %s",
+                      "block-autoconnect: blocking autoconnect of connection '%s': %s",
                       nm_settings_connection_get_id(sett_conn),
                       NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_string,
                                             nm_device_state_reason_check(reason)));
-                nm_settings_connection_autoconnect_blocked_reason_set(sett_conn,
-                                                                      blocked_reason,
-                                                                      TRUE);
+                if (blocked_reason == NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED)
+                    nm_manager_devcon_autoconnect_blocked_reason_set(priv->manager,
+                                                                     device,
+                                                                     sett_conn,
+                                                                     blocked_reason,
+                                                                     TRUE);
+                else
+                    nm_settings_connection_autoconnect_blocked_reason_set(sett_conn,
+                                                                          blocked_reason,
+                                                                          TRUE);
             }
         }
         ip6_remove_device_prefix_delegations(self, device);
@@ -2126,7 +2189,7 @@ device_state_changed(NMDevice           *device,
             update_routing_and_dns(self, FALSE, device);
 
         /* Device is now available for auto-activation */
-        schedule_activate_check(self, device);
+        nm_policy_device_recheck_auto_activate_schedule(self, device);
         break;
 
     case NM_DEVICE_STATE_PREPARE:
@@ -2146,9 +2209,11 @@ device_state_changed(NMDevice           *device,
     case NM_DEVICE_STATE_IP_CONFIG:
         /* We must have secrets if we got here. */
         if (sett_conn)
-            nm_settings_connection_autoconnect_blocked_reason_set(
+            nm_manager_devcon_autoconnect_blocked_reason_set(
+                priv->manager,
+                device,
                 sett_conn,
-                NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL,
+                NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_FAILED,
                 FALSE);
         break;
     case NM_DEVICE_STATE_SECONDARIES:
@@ -2248,16 +2313,7 @@ device_autoconnect_changed(NMDevice *device, GParamSpec *pspec, gpointer user_da
     NMPolicyPrivate *priv = user_data;
     NMPolicy        *self = _PRIV_TO_SELF(priv);
 
-    schedule_activate_check(self, device);
-}
-
-static void
-device_recheck_auto_activate(NMDevice *device, gpointer user_data)
-{
-    NMPolicyPrivate *priv = user_data;
-    NMPolicy        *self = _PRIV_TO_SELF(priv);
-
-    schedule_activate_check(self, device);
+    nm_policy_device_recheck_auto_activate_schedule(self, device);
 }
 
 static void
@@ -2292,10 +2348,6 @@ devices_list_register(NMPolicy *self, NMDevice *device)
                      "notify::" NM_DEVICE_AUTOCONNECT,
                      G_CALLBACK(device_autoconnect_changed),
                      priv);
-    g_signal_connect(device,
-                     NM_DEVICE_RECHECK_AUTO_ACTIVATE,
-                     G_CALLBACK(device_recheck_auto_activate),
-                     priv);
 }
 
 static void
@@ -2319,16 +2371,13 @@ device_removed(NMManager *manager, NMDevice *device, gpointer user_data)
 {
     NMPolicyPrivate *priv = user_data;
     NMPolicy        *self = _PRIV_TO_SELF(priv);
-    ActivateData    *data;
 
     /* TODO: is this needed? The delegations are cleaned up
      * on transition to deactivated too. */
     ip6_remove_device_prefix_delegations(self, device);
 
-    /* Clear any idle callbacks for this device */
-    data = find_pending_activation(self, device);
-    if (data && data->autoactivate_id)
-        activate_data_free(data);
+    if (c_list_is_linked(&device->policy_auto_activate_lst))
+        _auto_activate_device_clear(self, device, FALSE);
 
     if (g_hash_table_remove(priv->devices, device))
         devices_list_unregister(self, device);
@@ -2508,39 +2557,45 @@ active_connection_removed(NMManager *manager, NMActiveConnection *active, gpoint
 /*****************************************************************************/
 
 static gboolean
-schedule_activate_all_cb(gpointer user_data)
+_device_recheck_auto_activate_all_cb(gpointer user_data)
 {
     NMPolicy        *self = user_data;
     NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
     const CList     *tmp_lst;
     NMDevice        *device;
 
-    priv->schedule_activate_all_id = 0;
+    nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source);
 
     nm_manager_for_each_device (priv->manager, device, tmp_lst)
-        schedule_activate_check(self, device);
+        nm_policy_device_recheck_auto_activate_schedule(self, device);
 
-    return G_SOURCE_REMOVE;
+    return G_SOURCE_CONTINUE;
 }
 
 static void
-schedule_activate_all(NMPolicy *self)
+nm_policy_device_recheck_auto_activate_all_schedule(NMPolicy *self)
 {
     NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
 
     /* always restart the idle handler. That way, we settle
      * all other events before restarting to activate them. */
-    nm_clear_g_source(&priv->schedule_activate_all_id);
-    priv->schedule_activate_all_id = g_idle_add(schedule_activate_all_cb, self);
+    nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source);
+
+    priv->device_recheck_auto_activate_all_idle_source =
+        nm_g_idle_add_source(_device_recheck_auto_activate_all_cb, self);
 }
 
+/*****************************************************************************/
+
 static void
 connection_added(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data)
 {
     NMPolicyPrivate *priv = user_data;
     NMPolicy        *self = _PRIV_TO_SELF(priv);
 
-    schedule_activate_all(self);
+    unblock_autoconnect_for_ports_for_sett_conn(self, connection);
+
+    nm_policy_device_recheck_auto_activate_all_schedule(self);
 }
 
 static void
@@ -2597,6 +2652,8 @@ connection_updated(NMSettings           *settings,
     NMPolicy                        *self          = _PRIV_TO_SELF(priv);
     NMSettingsConnectionUpdateReason update_reason = update_reason_u;
 
+    unblock_autoconnect_for_ports_for_sett_conn(self, connection);
+
     if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)) {
         const CList *tmp_lst;
         NMDevice    *device;
@@ -2608,44 +2665,15 @@ connection_updated(NMSettings           *settings,
         }
     }
 
-    schedule_activate_all(self);
-}
-
-static void
-_deactivate_if_active(NMPolicy *self, NMSettingsConnection *connection)
-{
-    NMPolicyPrivate    *priv = NM_POLICY_GET_PRIVATE(self);
-    NMActiveConnection *ac;
-    const CList        *tmp_list, *tmp_safe;
-    GError             *error = NULL;
-
-    nm_assert(NM_IS_SETTINGS_CONNECTION(connection));
-
-    nm_manager_for_each_active_connection_safe (priv->manager, ac, tmp_list, tmp_safe) {
-        if (nm_active_connection_get_settings_connection(ac) == connection
-            && (nm_active_connection_get_state(ac) <= NM_ACTIVE_CONNECTION_STATE_ACTIVATED)) {
-            if (!nm_manager_deactivate_connection(priv->manager,
-                                                  ac,
-                                                  NM_DEVICE_STATE_REASON_CONNECTION_REMOVED,
-                                                  &error)) {
-                _LOGW(LOGD_DEVICE,
-                      "connection '%s' disappeared, but error deactivating it: (%d) %s",
-                      nm_settings_connection_get_id(connection),
-                      error ? error->code : -1,
-                      error ? error->message : "(unknown)");
-                g_clear_error(&error);
-            }
-        }
-    }
+    nm_policy_device_recheck_auto_activate_all_schedule(self);
 }
 
 static void
 connection_removed(NMSettings *settings, NMSettingsConnection *connection, gpointer user_data)
 {
     NMPolicyPrivate *priv = user_data;
-    NMPolicy        *self = _PRIV_TO_SELF(priv);
 
-    _deactivate_if_active(self, connection);
+    nm_manager_deactivate_ac(priv->manager, connection);
 }
 
 static void
@@ -2657,7 +2685,7 @@ connection_flags_changed(NMSettings *settings, NMSettingsConnection *connection,
     if (NM_FLAGS_HAS(nm_settings_connection_get_flags(connection),
                      NM_SETTINGS_CONNECTION_INT_FLAGS_VISIBLE)) {
         if (!nm_settings_connection_autoconnect_is_blocked(connection))
-            schedule_activate_all(self);
+            nm_policy_device_recheck_auto_activate_all_schedule(self);
     }
 }
 
@@ -2671,7 +2699,7 @@ secret_agent_registered(NMSettings *settings, NMSecretAgent *agent, gpointer use
      * connections failed due to missing secrets may re-try auto-connection.
      */
     if (reset_autoconnect_all(self, NULL, TRUE))
-        schedule_activate_all(self);
+        nm_policy_device_recheck_auto_activate_all_schedule(self);
 }
 
 NMActiveConnection *
@@ -2765,7 +2793,7 @@ nm_policy_init(NMPolicy *self)
     NMPolicyPrivate *priv          = NM_POLICY_GET_PRIVATE(self);
     gs_free char    *hostname_mode = NULL;
 
-    c_list_init(&priv->pending_activation_checks);
+    c_list_init(&priv->policy_auto_activate_lst_head);
 
     priv->netns = g_object_ref(nm_netns_get());
 
@@ -2901,9 +2929,9 @@ dispose(GObject *object)
 {
     NMPolicy        *self = NM_POLICY(object);
     NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
-    GHashTableIter   h_iter;
-    NMDevice        *device;
-    ActivateData    *data, *data_safe;
+
+    nm_assert(!c_list_is_empty(&priv->policy_auto_activate_lst_head));
+    nm_assert(g_hash_table_size(priv->devices) == 0);
 
     nm_clear_g_object(&priv->default_ac4);
     nm_clear_g_object(&priv->default_ac6);
@@ -2911,9 +2939,6 @@ dispose(GObject *object)
     nm_clear_g_object(&priv->activating_ac6);
     nm_clear_pointer(&priv->pending_active_connections, g_hash_table_unref);
 
-    c_list_for_each_entry_safe (data, data_safe, &priv->pending_activation_checks, pending_lst)
-        activate_data_free(data);
-
     g_slist_free_full(priv->pending_secondaries, (GDestroyNotify) pending_secondary_data_free);
     priv->pending_secondaries = NULL;
 
@@ -2932,20 +2957,14 @@ dispose(GObject *object)
         g_clear_object(&priv->dns_manager);
     }
 
-    g_hash_table_iter_init(&h_iter, priv->devices);
-    while (g_hash_table_iter_next(&h_iter, (gpointer *) &device, NULL)) {
-        g_hash_table_iter_remove(&h_iter);
-        devices_list_unregister(self, device);
-    }
-
     /* The manager should have disposed of ActiveConnections already, which
      * will have called active_connection_removed() and thus we don't need
      * to clean anything up.  Assert that this is TRUE.
      */
     nm_assert(c_list_is_empty(nm_manager_get_active_connections(priv->manager)));
 
-    nm_clear_g_source(&priv->reset_retries_id);
-    nm_clear_g_source(&priv->schedule_activate_all_id);
+    nm_clear_g_source_inst(&priv->reset_connections_retries_idle_source);
+    nm_clear_g_source_inst(&priv->device_recheck_auto_activate_all_idle_source);
 
     nm_clear_g_free(&priv->orig_hostname);
     nm_clear_g_free(&priv->cur_hostname);
diff --git a/src/core/nm-policy.h b/src/core/nm-policy.h
index f2b98dbd..9cfb0b24 100644
--- a/src/core/nm-policy.h
+++ b/src/core/nm-policy.h
@@ -34,6 +34,8 @@ NMActiveConnection *nm_policy_get_activating_ip6_ac(NMPolicy *policy);
 
 void nm_policy_unblock_failed_ovs_interfaces(NMPolicy *self);
 
+void nm_policy_device_recheck_auto_activate_schedule(NMPolicy *self, NMDevice *device);
+
 /**
  * NMPolicyHostnameMode
  * @NM_POLICY_HOSTNAME_MODE_NONE: never update the transient hostname.
diff --git a/src/core/nm-test-utils-core.h b/src/core/nm-test-utils-core.h
index e341d62f..176f4320 100644
--- a/src/core/nm-test-utils-core.h
+++ b/src/core/nm-test-utils-core.h
@@ -49,8 +49,8 @@ nmtst_platform_ip4_address_full(const char      *address,
 {
     NMPlatformIP4Address *addr = nmtst_platform_ip4_address(address, peer_address, plen);
 
-    G_STATIC_ASSERT(NMP_IFNAMSIZ == sizeof(addr->label));
-    g_assert(!label || strlen(label) < NMP_IFNAMSIZ);
+    G_STATIC_ASSERT(NM_IFNAMSIZ == sizeof(addr->label));
+    g_assert(!label || strlen(label) < NM_IFNAMSIZ);
 
     addr->ifindex     = ifindex;
     addr->addr_source = source;
diff --git a/src/core/platform/nm-fake-platform.c b/src/core/platform/nm-fake-platform.c
index 0c366d82..f6f377d4 100644
--- a/src/core/platform/nm-fake-platform.c
+++ b/src/core/platform/nm-fake-platform.c
@@ -102,7 +102,9 @@ static gboolean ip6_address_add(NMPlatform     *platform,
                                 struct in6_addr peer_addr,
                                 guint32         lifetime,
                                 guint32         preferred,
-                                guint           flags);
+                                guint           flags,
+                                char          **out_extack_msg);
+
 static gboolean
 ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen);
 
@@ -276,6 +278,24 @@ link_add_pre(NMPlatform *platform,
     return device;
 }
 
+static void
+link_add_post(NMPlatform *self, NMFakePlatformLink *device)
+{
+    char path[128];
+
+    switch (device->obj->link.type) {
+    case NM_LINK_TYPE_BRIDGE:
+        nm_sprintf_buf(path, "/sys/class/net/%s/bridge/default_pvid", device->obj->link.name);
+        sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), "1");
+
+        nm_sprintf_buf(path, "/sys/class/net/%s/bridge/vlan_filtering", device->obj->link.name);
+        sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), "0");
+        break;
+    default:
+        break;
+    }
+}
+
 static int
 link_add(NMPlatform            *platform,
          NMLinkType             type,
@@ -387,6 +407,7 @@ link_add(NMPlatform            *platform,
         *out_link = NMP_OBJECT_CAST_LINK(device->obj);
 
     link_changed(platform, device, cache_op, NULL);
+    link_add_post(platform, device);
     if (veth_peer)
         link_changed(platform, device_veth, cache_op_veth, NULL);
 
@@ -439,6 +460,7 @@ link_add_one(NMPlatform *platform,
 static gboolean
 link_delete(NMPlatform *platform, int ifindex)
 {
+    NMFakePlatformPrivate          *priv     = NM_FAKE_PLATFORM_GET_PRIVATE(platform);
     NMFakePlatformLink             *device   = link_get(platform, ifindex);
     nm_auto_nmpobj const NMPObject *obj_old  = NULL;
     nm_auto_nmpobj const NMPObject *obj_old2 = NULL;
@@ -449,6 +471,17 @@ link_delete(NMPlatform *platform, int ifindex)
 
     obj_old = g_steal_pointer(&device->obj);
 
+    if (obj_old->link.type == NM_LINK_TYPE_BRIDGE) {
+        char path[128];
+
+        g_hash_table_remove(
+            priv->options,
+            nm_sprintf_buf(path, "/sys/class/net/%s/bridge/default_pvid", obj_old->link.name));
+        g_hash_table_remove(
+            priv->options,
+            nm_sprintf_buf(path, "/sys/class/net/%s/bridge/vlan_filtering", obj_old->link.name));
+    }
+
     cache_op = nmp_cache_remove(nm_platform_get_cache(platform), obj_old, FALSE, FALSE, &obj_old2);
     g_assert(cache_op == NMP_CACHE_OPS_REMOVED);
     g_assert(obj_old2);
@@ -542,7 +575,7 @@ link_changed(NMPlatform         *platform,
     nm_platform_cache_update_emit_signal(platform, cache_op, obj_old, device->obj);
 
     if (!IN6_IS_ADDR_UNSPECIFIED(&device->ip6_lladdr)) {
-        if (device->obj->link.connected)
+        if (device->obj->link.connected) {
             ip6_address_add(platform,
                             device->obj->link.ifindex,
                             device->ip6_lladdr,
@@ -550,8 +583,9 @@ link_changed(NMPlatform         *platform,
                             in6addr_any,
                             NM_PLATFORM_LIFETIME_PERMANENT,
                             NM_PLATFORM_LIFETIME_PERMANENT,
-                            0);
-        else
+                            0,
+                            NULL);
+        } else
             ip6_address_delete(platform, device->obj->link.ifindex, device->ip6_lladdr, 64);
     }
 
@@ -669,8 +703,10 @@ link_supports_sriov(NMPlatform *platform, int ifindex)
 static gboolean
 link_change(NMPlatform                   *platform,
             int                           ifindex,
+            NMPlatformLinkProps          *props,
             NMPortKind                    port_kind,
-            const NMPlatformLinkPortData *port_data)
+            const NMPlatformLinkPortData *port_data,
+            NMPlatformLinkChangeFlags     flags)
 {
     NMFakePlatformLink       *device  = link_get(platform, ifindex);
     nm_auto_nmpobj NMPObject *obj_tmp = NULL;
@@ -747,6 +783,34 @@ link_vlan_change(NMPlatform             *platform,
     return FALSE;
 }
 
+static gboolean
+link_set_bridge_info(NMPlatform                            *self,
+                     int                                    ifindex,
+                     const NMPlatformLinkSetBridgeInfoData *bridge_info)
+{
+    NMFakePlatformLink *link;
+    char                path[128];
+    char                value[128];
+
+    link = link_get(self, ifindex);
+    if (!link)
+        return FALSE;
+
+    if (bridge_info->vlan_default_pvid_has) {
+        nm_sprintf_buf(path, "/sys/class/net/%s/bridge/default_pvid", link->obj->link.name);
+        nm_sprintf_buf(value, "%u", bridge_info->vlan_default_pvid_val);
+        sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), value);
+    }
+
+    if (bridge_info->vlan_filtering_has) {
+        nm_sprintf_buf(path, "/sys/class/net/%s/bridge/vlan_filtering", link->obj->link.name);
+        nm_sprintf_buf(value, "%u", bridge_info->vlan_filtering_val);
+        sysctl_set(self, NMP_SYSCTL_PATHID_ABSOLUTE(path), value);
+    }
+
+    return TRUE;
+}
+
 struct infiniband_add_data {
     int parent;
     int p_key;
@@ -785,7 +849,7 @@ infiniband_partition_add(NMPlatform            *platform,
     parent_device = link_get(platform, parent);
     g_return_val_if_fail(parent_device != NULL, FALSE);
 
-    nmp_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key);
+    nm_net_devname_infiniband(name, parent_device->obj->link.name, p_key);
 
     link_add_one(platform, name, NM_LINK_TYPE_INFINIBAND, _infiniband_add_prepare, &d, out_link);
     return TRUE;
@@ -800,7 +864,7 @@ infiniband_partition_delete(NMPlatform *platform, int parent, int p_key)
     parent_device = link_get(platform, parent);
     g_return_val_if_fail(parent_device != NULL, FALSE);
 
-    nmp_utils_new_infiniband_name(name, parent_device->obj->link.name, p_key);
+    nm_net_devname_infiniband(name, parent_device->obj->link.name, p_key);
     return link_delete(platform, nm_platform_link_get_ifindex(platform, name));
 }
 
@@ -890,7 +954,10 @@ mesh_set_ssid(NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 /*****************************************************************************/
 
 static gboolean
-ipx_address_add(NMPlatform *platform, int addr_family, const NMPlatformObject *address)
+ipx_address_add(NMPlatform             *platform,
+                int                     addr_family,
+                const NMPlatformObject *address,
+                char                  **out_extack_msg)
 {
     nm_auto_nmpobj NMPObject       *obj = NULL;
     NMPCacheOpsType                 cache_op;
@@ -899,6 +966,7 @@ ipx_address_add(NMPlatform *platform, int addr_family, const NMPlatformObject *a
     NMPCache                       *cache   = nm_platform_get_cache(platform);
 
     g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
+    g_assert(!out_extack_msg || !*out_extack_msg);
 
     obj = nmp_object_new(addr_family == AF_INET ? NMP_OBJECT_TYPE_IP4_ADDRESS
                                                 : NMP_OBJECT_TYPE_IP6_ADDRESS,
@@ -919,7 +987,8 @@ ip4_address_add(NMPlatform *platform,
                 guint32     lifetime,
                 guint32     preferred,
                 guint32     flags,
-                const char *label)
+                const char *label,
+                char      **out_extack_msg)
 {
     NMPlatformIP4Address address;
 
@@ -939,7 +1008,7 @@ ip4_address_add(NMPlatform *platform,
     if (label)
         g_strlcpy(address.label, label, sizeof(address.label));
 
-    return ipx_address_add(platform, AF_INET, (const NMPlatformObject *) &address);
+    return ipx_address_add(platform, AF_INET, (const NMPlatformObject *) &address, out_extack_msg);
 }
 
 static gboolean
@@ -950,7 +1019,8 @@ ip6_address_add(NMPlatform     *platform,
                 struct in6_addr peer_addr,
                 guint32         lifetime,
                 guint32         preferred,
-                guint32         flags)
+                guint32         flags,
+                char          **out_extack_msg)
 {
     NMPlatformIP6Address address;
 
@@ -967,7 +1037,7 @@ ip6_address_add(NMPlatform     *platform,
     address.preferred   = preferred;
     address.n_ifa_flags = flags;
 
-    return ipx_address_add(platform, AF_INET6, (const NMPlatformObject *) &address);
+    return ipx_address_add(platform, AF_INET6, (const NMPlatformObject *) &address, out_extack_msg);
 }
 
 static gboolean
@@ -1117,7 +1187,7 @@ object_delete(NMPlatform *platform, const NMPObject *obj)
 }
 
 static int
-ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack)
+ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack, char **out_extack_msg)
 {
     NMDedupMultiIter                iter;
     nm_auto_nmpobj NMPObject       *obj = NULL;
@@ -1139,6 +1209,7 @@ ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack)
     g_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_stack),
                        NMP_OBJECT_TYPE_IP4_ROUTE,
                        NMP_OBJECT_TYPE_IP6_ROUTE));
+    g_assert(!out_extack_msg || !*out_extack_msg);
 
     addr_family = NMP_OBJECT_GET_ADDR_FAMILY(obj_stack);
 
@@ -1364,6 +1435,8 @@ nm_fake_platform_class_init(NMFakePlatformClass *klass)
 
     platform_class->link_vlan_change = link_vlan_change;
 
+    platform_class->link_set_bridge_info = link_set_bridge_info;
+
     platform_class->infiniband_partition_add    = infiniband_partition_add;
     platform_class->infiniband_partition_delete = infiniband_partition_delete;
 
diff --git a/src/core/platform/tests/test-cleanup.c b/src/core/platform/tests/test-cleanup.c
index 139a0280..c643e71c 100644
--- a/src/core/platform/tests/test-cleanup.c
+++ b/src/core/platform/tests/test-cleanup.c
@@ -77,6 +77,7 @@ test_cleanup_internal(void)
                                          lifetime,
                                          preferred,
                                          0,
+                                         NULL,
                                          NULL));
     g_assert(nm_platform_ip6_address_add(NM_PLATFORM_GET,
                                          ifindex,
@@ -85,7 +86,8 @@ test_cleanup_internal(void)
                                          in6addr_any,
                                          lifetime,
                                          preferred,
-                                         flags));
+                                         flags,
+                                         NULL));
     nmtstp_ip4_route_add(NM_PLATFORM_GET,
                          ifindex,
                          NM_IP_CONFIG_SOURCE_USER,
diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c
index 65bdfeae..fde7dc0d 100644
--- a/src/core/platform/tests/test-common.c
+++ b/src/core/platform/tests/test-common.c
@@ -1809,7 +1809,8 @@ _ip_address_add(NMPlatform     *platform,
                                                   lifetime,
                                                   preferred,
                                                   flags,
-                                                  label);
+                                                  label,
+                                                  NULL);
         } else {
             g_assert(label == NULL);
             success = nm_platform_ip6_address_add(platform,
@@ -1819,7 +1820,8 @@ _ip_address_add(NMPlatform     *platform,
                                                   peer_address->addr6,
                                                   lifetime,
                                                   preferred,
-                                                  flags);
+                                                  flags,
+                                                  NULL);
         }
         g_assert(success);
     }
diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c
index 33858671..8a54ac48 100644
--- a/src/core/platform/tests/test-link.c
+++ b/src/core/platform/tests/test-link.c
@@ -36,6 +36,16 @@
 #define _ADD_DUMMY(platform, name) \
     g_assert(NMTST_NM_ERR_SUCCESS(nm_platform_link_dummy_add((platform), (name), NULL)))
 
+#define _sysctl_assert_eq(plat, path, value)                                   \
+    G_STMT_START                                                               \
+    {                                                                          \
+        gs_free char *_val = NULL;                                             \
+                                                                               \
+        _val = nm_platform_sysctl_get(plat, NMP_SYSCTL_PATHID_ABSOLUTE(path)); \
+        g_assert_cmpstr(_val, ==, value);                                      \
+    }                                                                          \
+    G_STMT_END
+
 static void
 test_bogus(void)
 {
@@ -212,58 +222,58 @@ test_link_changed_signal_cb(NMPlatform               *platform,
 }
 
 static void
-test_slave(int master, int type, SignalData *master_changed)
+test_port(int controller, int port_type, SignalData *controller_changed)
 {
-    int         ifindex;
+    int         ifindex_port;
     SignalData *link_added = add_signal_ifname(NM_PLATFORM_SIGNAL_LINK_CHANGED,
                                                NM_PLATFORM_SIGNAL_ADDED,
                                                link_callback,
                                                SLAVE_NAME);
     SignalData *link_changed, *link_removed;
     char       *value;
-    NMLinkType  link_type = nm_platform_link_get_type(NM_PLATFORM_GET, master);
+    NMLinkType  controller_type = nm_platform_link_get_type(NM_PLATFORM_GET, controller);
     gboolean    test_link_changed_signal_arg1;
     gboolean    test_link_changed_signal_arg2;
 
-    g_assert(NM_IN_SET(link_type, NM_LINK_TYPE_TEAM, NM_LINK_TYPE_BOND, NM_LINK_TYPE_BRIDGE));
+    g_assert(NM_IN_SET(controller_type, NM_LINK_TYPE_TEAM, NM_LINK_TYPE_BOND, NM_LINK_TYPE_BRIDGE));
 
-    g_assert(software_add(type, SLAVE_NAME));
-    ifindex = nm_platform_link_get_ifindex(NM_PLATFORM_GET, SLAVE_NAME);
-    g_assert(ifindex > 0);
+    g_assert(software_add(port_type, SLAVE_NAME));
+    ifindex_port = nm_platform_link_get_ifindex(NM_PLATFORM_GET, SLAVE_NAME);
+    g_assert(ifindex_port > 0);
     link_changed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED,
                                       NM_PLATFORM_SIGNAL_CHANGED,
                                       link_callback,
-                                      ifindex);
+                                      ifindex_port);
     link_removed = add_signal_ifindex(NM_PLATFORM_SIGNAL_LINK_CHANGED,
                                       NM_PLATFORM_SIGNAL_REMOVED,
                                       link_callback,
-                                      ifindex);
+                                      ifindex_port);
     accept_signal(link_added);
 
-    /* Set the slave up to see whether master's IFF_LOWER_UP is set correctly.
+    /* Set the port up to see whether controller's IFF_LOWER_UP is set correctly.
      *
      * See https://bugzilla.redhat.com/show_bug.cgi?id=910348
      */
-    g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
-    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, FALSE) >= 0);
-    g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
+    g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port));
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex_port, IFF_UP, FALSE) >= 0);
+    g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port));
     ensure_no_signal(link_changed);
 
-    /* Enslave */
-    link_changed->ifindex = ifindex;
-    g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, master, ifindex));
-    g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex), ==, master);
+    /* Attach port */
+    link_changed->ifindex = ifindex_port;
+    g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, controller, ifindex_port));
+    g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex_port), ==, controller);
 
     accept_signals(link_changed, 1, 3);
-    accept_signals(master_changed, 0, 2);
+    accept_signals(controller_changed, 0, 2);
 
-    /* enslaveing brings put the slave */
-    if (NM_IN_SET(link_type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM))
-        g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
+    /* Attaching ports brings up the port */
+    if (NM_IN_SET(controller_type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM))
+        g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port));
     else
-        g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
+        g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port));
 
-    if (NM_IN_SET(link_type, NM_LINK_TYPE_BOND)) {
+    if (NM_IN_SET(controller_type, NM_LINK_TYPE_BOND)) {
         NMPlatformLinkBondPort   bond_port;
         gboolean                 prio_has;
         gboolean                 prio_supported;
@@ -273,7 +283,7 @@ test_slave(int master, int type, SignalData *master_changed)
         link = nmtstp_link_get_typed(NM_PLATFORM_GET, 0, SLAVE_NAME, NM_LINK_TYPE_DUMMY);
         g_assert(link);
 
-        lnk = nm_platform_link_get_lnk_bond(NM_PLATFORM_GET, master, NULL);
+        lnk = nm_platform_link_get_lnk_bond(NM_PLATFORM_GET, controller, NULL);
         g_assert(lnk);
 
         g_assert(NM_IN_SET(lnk->mode, 3, 1));
@@ -286,13 +296,24 @@ test_slave(int master, int type, SignalData *master_changed)
             .prio     = prio_has ? 6 : 0,
         };
 
-        g_assert(nm_platform_link_change(NM_PLATFORM_GET, ifindex, &bond_port));
+        g_assert(nm_platform_link_change(NM_PLATFORM_GET, ifindex_port, NULL, &bond_port, 0));
         accept_signals(link_changed, 1, 3);
 
-        link = nmtstp_link_get(NM_PLATFORM_GET, ifindex, SLAVE_NAME);
+        link = nmtstp_link_get(NM_PLATFORM_GET, ifindex_port, SLAVE_NAME);
         g_assert(link);
         g_assert_cmpint(link->port_data.bond.queue_id, ==, 5);
         g_assert(link->port_data.bond.prio_has || link->port_data.bond.prio == 0);
+    } else if (controller_type == NM_LINK_TYPE_BRIDGE) {
+        /* Skip this part for nm-fake-platform */
+        if (nmtstp_is_root_test() && nmtstp_is_sysfs_writable()) {
+            g_assert(nm_platform_sysctl_slave_set_option(NM_PLATFORM_GET,
+                                                         ifindex_port,
+                                                         "priority",
+                                                         "614"));
+            value = nm_platform_sysctl_slave_get_option(NM_PLATFORM_GET, ifindex_port, "priority");
+            g_assert_cmpstr(value, ==, "614");
+            g_free(value);
+        }
     }
 
     test_link_changed_signal_arg1 = FALSE;
@@ -306,10 +327,10 @@ test_slave(int master, int type, SignalData *master_changed)
                      G_CALLBACK(test_link_changed_signal_cb),
                      &test_link_changed_signal_arg2);
 
-    /* Set master up */
-    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, master, IFF_UP, TRUE) >= 0);
-    g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, master));
-    accept_signals(master_changed, 1, 3);
+    /* Set controller up */
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, controller, IFF_UP, TRUE) >= 0);
+    g_assert(nm_platform_link_is_up(NM_PLATFORM_GET, controller));
+    accept_signals(controller_changed, 1, 3);
 
     g_signal_handlers_disconnect_by_func(NM_PLATFORM_GET,
                                          G_CALLBACK(test_link_changed_signal_cb),
@@ -320,28 +341,28 @@ test_slave(int master, int type, SignalData *master_changed)
     g_assert(test_link_changed_signal_arg1);
     g_assert(test_link_changed_signal_arg2);
 
-    /* Master with a disconnected slave is disconnected
+    /* Master with a disconnected port is disconnected
      *
-     * For some reason, bonding and teaming slaves are automatically set up. We
+     * For some reason, bonding and teaming ports are automatically set up. We
      * need to set them back down for this test.
      */
-    switch (nm_platform_link_get_type(NM_PLATFORM_GET, master)) {
+    switch (nm_platform_link_get_type(NM_PLATFORM_GET, controller)) {
     case NM_LINK_TYPE_BOND:
     case NM_LINK_TYPE_TEAM:
-        g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, FALSE) >= 0);
+        g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex_port, IFF_UP, FALSE) >= 0);
         accept_signal(link_changed);
-        accept_signals(master_changed, 0, 3);
+        accept_signals(controller_changed, 0, 3);
         break;
     default:
         break;
     }
-    g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex));
-    g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex));
-    if (nmtstp_is_root_test() && nm_platform_link_is_connected(NM_PLATFORM_GET, master)) {
-        if (nm_platform_link_get_type(NM_PLATFORM_GET, master) == NM_LINK_TYPE_TEAM) {
-            /* Older team versions (e.g. Fedora 17) have a bug that team master stays
-             * IFF_LOWER_UP even if its slave is down. Double check it with iproute2 and if
-             * `ip link` also claims master to be up, accept it. */
+    g_assert(!nm_platform_link_is_up(NM_PLATFORM_GET, ifindex_port));
+    g_assert(!nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex_port));
+    if (nmtstp_is_root_test() && nm_platform_link_is_connected(NM_PLATFORM_GET, controller)) {
+        if (nm_platform_link_get_type(NM_PLATFORM_GET, controller) == NM_LINK_TYPE_TEAM) {
+            /* Older team versions (e.g. Fedora 17) have a bug that team controller stays
+             * IFF_LOWER_UP even if its port is down. Double check it with iproute2 and if
+             * `ip link` also claims controller to be up, accept it. */
             char *stdout_str = NULL;
 
             nmtst_spawn_sync(NULL,
@@ -352,7 +373,7 @@ test_slave(int master, int type, SignalData *master_changed)
                              "link",
                              "show",
                              "dev",
-                             nm_platform_link_get_name(NM_PLATFORM_GET, master));
+                             nm_platform_link_get_name(NM_PLATFORM_GET, controller));
 
             g_assert(strstr(stdout_str, "LOWER_UP"));
             g_free(stdout_str);
@@ -360,44 +381,29 @@ test_slave(int master, int type, SignalData *master_changed)
             g_assert_not_reached();
     }
 
-    /* Set slave up and see if master gets up too */
-    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex, IFF_UP, TRUE) >= 0);
-    g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex));
-    g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, master));
+    /* Set port up and see if controller gets up too */
+    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, ifindex_port, IFF_UP, TRUE) >= 0);
+    g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, ifindex_port));
+    g_assert(nm_platform_link_is_connected(NM_PLATFORM_GET, controller));
     accept_signals(link_changed, 1, 3);
     /* NM running, can cause additional change of addrgenmode */
-    accept_signals(master_changed, 0, 3);
+    accept_signals(controller_changed, 0, 3);
 
-    /* Enslave again
+    /* Attach port again
      *
-     * Gracefully succeed if already enslaved.
+     * Gracefully succeed if already attached port.
      */
     ensure_no_signal(link_changed);
-    g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, master, ifindex));
+    g_assert(nm_platform_link_enslave(NM_PLATFORM_GET, controller, ifindex_port));
     accept_signals(link_changed, 0, 2);
-    accept_signals(master_changed, 0, 2);
-
-    /* Set slave option */
-    switch (type) {
-    case NM_LINK_TYPE_BRIDGE:
-        if (nmtstp_is_sysfs_writable()) {
-            g_assert(
-                nm_platform_sysctl_slave_set_option(NM_PLATFORM_GET, ifindex, "priority", "614"));
-            value = nm_platform_sysctl_slave_get_option(NM_PLATFORM_GET, ifindex, "priority");
-            g_assert_cmpstr(value, ==, "614");
-            g_free(value);
-        }
-        break;
-    default:
-        break;
-    }
+    accept_signals(controller_changed, 0, 2);
 
     /* Release */
     ensure_no_signal(link_added);
     ensure_no_signal(link_changed);
     ensure_no_signal(link_removed);
-    g_assert(nm_platform_link_release(NM_PLATFORM_GET, master, ifindex));
-    g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex), ==, 0);
+    g_assert(nm_platform_link_release(NM_PLATFORM_GET, controller, ifindex_port));
+    g_assert_cmpint(nm_platform_link_get_master(NM_PLATFORM_GET, ifindex_port), ==, 0);
     if (link_changed->received_count > 0) {
         accept_signals(link_added, 0, 1);
         accept_signals(link_changed, 1, 5);
@@ -409,22 +415,22 @@ test_slave(int master, int type, SignalData *master_changed)
         ensure_no_signal(link_changed);
         accept_signal(link_removed);
     }
-    accept_signals(master_changed, 0, 3);
+    accept_signals(controller_changed, 0, 3);
 
-    ensure_no_signal(master_changed);
+    ensure_no_signal(controller_changed);
 
     /* Release again */
     ensure_no_signal(link_changed);
-    g_assert(!nm_platform_link_release(NM_PLATFORM_GET, master, ifindex));
+    g_assert(!nm_platform_link_release(NM_PLATFORM_GET, controller, ifindex_port));
 
-    ensure_no_signal(master_changed);
+    ensure_no_signal(controller_changed);
 
     /* Remove */
     ensure_no_signal(link_added);
     ensure_no_signal(link_changed);
     ensure_no_signal(link_removed);
-    nmtstp_link_delete(NULL, -1, ifindex, NULL, TRUE);
-    accept_signals(master_changed, 0, 1);
+    nmtstp_link_delete(NULL, -1, ifindex_port, NULL, TRUE);
+    accept_signals(controller_changed, 0, 1);
     accept_signals(link_changed, 0, 1);
     accept_signal(link_removed);
 
@@ -528,7 +534,7 @@ test_software(NMLinkType link_type, const char *link_typename)
     case NM_LINK_TYPE_BOND:
     case NM_LINK_TYPE_TEAM:
         link_changed->ifindex = ifindex;
-        test_slave(ifindex, NM_LINK_TYPE_DUMMY, link_changed);
+        test_port(ifindex, NM_LINK_TYPE_DUMMY, link_changed);
         link_changed->ifindex = 0;
         break;
     default:
@@ -618,10 +624,14 @@ test_vlan(void)
 static void
 test_bridge_addr(void)
 {
-    char                  addr[ETH_ALEN];
-    NMPlatformLink        link;
-    const NMPlatformLink *plink = NULL;
-    NMPLinkAddress        hw_perm_addr;
+    char                            addr[ETH_ALEN];
+    NMPlatformLink                  link;
+    const NMPlatformLink           *plink = NULL;
+    NMPLinkAddress                  hw_perm_addr;
+    gboolean                        b;
+    char                            sbuf[100];
+    gs_free char                   *str = NULL;
+    NMPlatformLinkSetBridgeInfoData info_data;
 
     nm_utils_hwaddr_aton("de:ad:be:ef:00:11", addr, sizeof(addr));
 
@@ -692,6 +702,42 @@ test_bridge_addr(void)
     g_assert_cmpint(plink->l_address.len, ==, sizeof(addr));
     g_assert(!memcmp(plink->l_address.data, addr, sizeof(addr)));
 
+    info_data = (const NMPlatformLinkSetBridgeInfoData){
+        .vlan_default_pvid_val = nmtst_rand_select(0, 5, 42, 1048),
+        .vlan_default_pvid_has = nmtst_get_rand_bool(),
+        .vlan_filtering_val    = nmtst_get_rand_bool(),
+        .vlan_filtering_has    = nmtst_get_rand_bool(),
+    };
+    b = nm_platform_link_set_bridge_info(NM_PLATFORM_GET, link.ifindex, &info_data);
+    g_assert(b);
+
+    _sysctl_assert_eq(NM_PLATFORM_GET,
+                      "/sys/class/net/" DEVICE_NAME "/bridge/default_pvid",
+                      info_data.vlan_default_pvid_has
+                          ? nm_sprintf_buf(sbuf, "%u", info_data.vlan_default_pvid_val)
+                          : "1");
+
+    _sysctl_assert_eq(NM_PLATFORM_GET,
+                      "/sys/class/net/" DEVICE_NAME "/bridge/vlan_filtering",
+                      info_data.vlan_filtering_val && info_data.vlan_filtering_has ? "1" : "0");
+
+    info_data = (const NMPlatformLinkSetBridgeInfoData){
+        .vlan_default_pvid_val = 55,
+        .vlan_default_pvid_has = TRUE,
+        .vlan_filtering_val    = !info_data.vlan_filtering_val,
+        .vlan_filtering_has    = TRUE,
+    };
+    b = nm_platform_link_set_bridge_info(NM_PLATFORM_GET, link.ifindex, &info_data);
+    g_assert(b);
+
+    _sysctl_assert_eq(NM_PLATFORM_GET,
+                      "/sys/class/net/" DEVICE_NAME "/bridge/default_pvid",
+                      nm_sprintf_buf(sbuf, "%u", info_data.vlan_default_pvid_val));
+
+    _sysctl_assert_eq(NM_PLATFORM_GET,
+                      "/sys/class/net/" DEVICE_NAME "/bridge/vlan_filtering",
+                      info_data.vlan_filtering_val ? "1" : "0");
+
     nmtstp_link_delete(NULL, -1, link.ifindex, link.name, TRUE);
 }
 
@@ -2630,6 +2676,36 @@ test_vlan_set_xgress(void)
 /*****************************************************************************/
 
 static void
+test_link_set_properties(void)
+{
+    const NMPlatformLink     *link;
+    NMPlatformLinkProps       props;
+    NMPlatformLinkChangeFlags flags;
+    int                       ifindex;
+
+    props = (NMPlatformLinkProps){
+        .tx_queue_length  = 599,
+        .gso_max_size     = 10001,
+        .gso_max_segments = 512,
+    };
+    flags = NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH | NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE
+            | NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS;
+
+    ifindex = nmtstp_link_dummy_add(NM_PLATFORM_GET, FALSE, "dummy1")->ifindex;
+    g_assert(nm_platform_link_change(NM_PLATFORM_GET, ifindex, &props, NULL, flags));
+
+    link = nmtstp_link_get(NM_PLATFORM_GET, ifindex, "dummy1");
+    g_assert(link);
+    g_assert_cmpint(link->link_props.tx_queue_length, ==, 599);
+    g_assert_cmpint(link->link_props.gso_max_size, ==, 10001);
+    g_assert_cmpint(link->link_props.gso_max_segments, ==, 512);
+
+    nmtstp_link_delete(NULL, -1, link->ifindex, "dummy1", TRUE);
+}
+
+/*****************************************************************************/
+
+static void
 test_create_many_links_do(guint n_devices)
 {
     gint64                 time, start_time = nm_utils_get_monotonic_timestamp_nsec();
@@ -2985,16 +3061,6 @@ _check_sysctl_skip(void)
 
 /*****************************************************************************/
 
-#define _sysctl_assert_eq(plat, path, value)                                   \
-    G_STMT_START                                                               \
-    {                                                                          \
-        gs_free char *_val = NULL;                                             \
-                                                                               \
-        _val = nm_platform_sysctl_get(plat, NMP_SYSCTL_PATHID_ABSOLUTE(path)); \
-        g_assert_cmpstr(_val, ==, value);                                      \
-    }                                                                          \
-    G_STMT_END
-
 static void
 test_netns_general(gpointer fixture, gconstpointer test_data)
 {
@@ -3533,7 +3599,7 @@ test_sysctl_rename(void)
                         ==,
                         (gint32) nm_platform_sysctl_get_int32(
                             PL,
-                            NMP_SYSCTL_PATHID_NETDIR(dirfd, s ?: "<unknown>", "ifindex"),
+                            NMP_SYSCTL_PATHID_NETDIR_A(dirfd, s ?: "<unknown>", "ifindex"),
                             -1));
         break;
     }
@@ -3605,7 +3671,7 @@ test_sysctl_netns_switch(void)
                     ==,
                     (gint32) nm_platform_sysctl_get_int32(
                         PL,
-                        NMP_SYSCTL_PATHID_NETDIR(dirfd, s ?: "<unknown>", "ifindex"),
+                        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, s ?: "<unknown>", "ifindex"),
                         -1));
     g_assert_cmpint(
         ifindex,
@@ -4022,6 +4088,8 @@ _nmtstp_setup_tests(void)
 
         g_test_add_func("/link/software/vlan/set-xgress", test_vlan_set_xgress);
 
+        g_test_add_func("/link/set-properties", test_link_set_properties);
+
         g_test_add_data_func("/link/create-many-links/20",
                              GUINT_TO_POINTER(20),
                              test_create_many_links);
diff --git a/src/core/platform/tests/test-route.c b/src/core/platform/tests/test-route.c
index bd8fdc27..9aa21a9a 100644
--- a/src/core/platform/tests/test-route.c
+++ b/src/core/platform/tests/test-route.c
@@ -421,7 +421,8 @@ test_ip6_route(void)
                                          in6addr_any,
                                          NM_PLATFORM_LIFETIME_PERMANENT,
                                          NM_PLATFORM_LIFETIME_PERMANENT,
-                                         0));
+                                         0,
+                                         NULL));
     accept_signals(route_added, 0, 3);
 
     _wait_for_ipv6_addr_non_tentative(NM_PLATFORM_GET, 200, ifindex, 1, &pref_src);
@@ -706,7 +707,8 @@ test_ip4_route_options(gconstpointer test_data)
                                              a->lifetime,
                                              a->preferred,
                                              a->n_ifa_flags,
-                                             a->label));
+                                             a->label,
+                                             NULL));
         if (a->peer_address == a->address)
             _wait_for_ipv4_addr_device_route(NM_PLATFORM_GET, 200, a->ifindex, a->address, a->plen);
     }
@@ -878,7 +880,8 @@ test_ip6_route_options(gconstpointer test_data)
                                              addr[i].peer_address,
                                              addr[i].lifetime,
                                              addr[i].preferred,
-                                             addr[i].n_ifa_flags));
+                                             addr[i].n_ifa_flags,
+                                             NULL));
     }
     _wait_for_ipv6_addr_non_tentative(NM_PLATFORM_GET, 400, IFINDEX, addr_n, addr_in6);
 
diff --git a/src/core/settings/nm-secret-agent.c b/src/core/settings/nm-secret-agent.c
index a3df4497..bb300345 100644
--- a/src/core/settings/nm-secret-agent.c
+++ b/src/core/settings/nm-secret-agent.c
@@ -463,7 +463,7 @@ _call_cancel_cb(GObject *source, GAsyncResult *result, gpointer user_data)
  * nm_secret_agent_cancel_call:
  * @self: the #NMSecretAgent instance for the @call_id.
  *   Maybe be %NULL if @call_id is %NULL.
- * @call_id: (allow-none): the call id to cancel. May be %NULL for convenience,
+ * @call_id: (nullable): the call id to cancel. May be %NULL for convenience,
  *   in which case it does nothing.
  *
  * It is an error to pass an invalid @call_id or a @call_id for an operation
diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c
index 024c0009..176cc2c2 100644
--- a/src/core/settings/nm-settings-connection.c
+++ b/src/core/settings/nm-settings-connection.c
@@ -24,13 +24,10 @@
 #include "libnm-core-intern/nm-core-internal.h"
 #include "nm-audit-manager.h"
 #include "nm-settings.h"
+#include "nm-manager.h"
 #include "nm-dbus-manager.h"
 #include "settings/plugins/keyfile/nms-keyfile-storage.h"
 
-#define AUTOCONNECT_RETRIES_UNSET       -2
-#define AUTOCONNECT_RETRIES_FOREVER     -1
-#define AUTOCONNECT_RESET_RETRIES_TIMER 300
-
 #define SEEN_BSSIDS_MAX 30
 
 #define _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES                          \
@@ -112,7 +109,11 @@ _seen_bssids_hash_new(void)
 
 /*****************************************************************************/
 
-NM_GOBJECT_PROPERTIES_DEFINE(NMSettingsConnection, PROP_UNSAVED, PROP_FLAGS, PROP_FILENAME, );
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettingsConnection,
+                             PROP_VERSION_ID,
+                             PROP_UNSAVED,
+                             PROP_FLAGS,
+                             PROP_FILENAME, );
 
 enum { UPDATED_INTERNAL, FLAGS_CHANGED, LAST_SIGNAL };
 
@@ -159,9 +160,7 @@ typedef struct _NMSettingsConnectionPrivate {
 
     guint64 last_secret_agent_version_id;
 
-    int autoconnect_retries;
-
-    gint32 autoconnect_retries_blocked_until;
+    guint64 version_id;
 
     bool timestamp_set : 1;
 
@@ -227,6 +226,22 @@ static guint _get_seen_bssids(NMSettingsConnection *self,
 
 /*****************************************************************************/
 
+NMSettings *
+nm_settings_connection_get_settings(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), NULL);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->settings;
+}
+
+NMManager *
+nm_settings_connection_get_manager(NMSettingsConnection *self)
+{
+    return nm_settings_get_manager(nm_settings_connection_get_settings(self));
+}
+
+/*****************************************************************************/
+
 NMDevice *
 nm_settings_connection_default_wired_get_device(NMSettingsConnection *self)
 {
@@ -361,6 +376,20 @@ nm_settings_connection_get_connection(NMSettingsConnection *self)
     return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->connection;
 }
 
+gpointer
+nm_settings_connection_get_setting(NMSettingsConnection *self, NMMetaSettingType meta_type)
+{
+    NMConnection *connection;
+
+    nm_assert(NM_IS_SETTINGS_CONNECTION(self));
+
+    connection = NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->connection;
+
+    nm_assert(NM_IS_SIMPLE_CONNECTION(connection));
+
+    return _nm_connection_get_setting_by_metatype_unsafe(connection, meta_type);
+}
+
 void
 _nm_settings_connection_set_connection(NMSettingsConnection            *self,
                                        NMConnection                    *new_connection,
@@ -1049,7 +1078,7 @@ get_secrets_idle_cb(NMSettingsConnectionCallId *call_id)
 /**
  * nm_settings_connection_get_secrets:
  * @self: the #NMSettingsConnection
- * @applied_connection: (allow-none): if provided, only request secrets
+ * @applied_connection: (nullable): if provided, only request secrets
  *   if @self equals to @applied_connection. Also, update the secrets
  *   in the @applied_connection.
  * @subject: the #NMAuthSubject originating the request
@@ -1414,6 +1443,7 @@ typedef struct {
     NMSettingsUpdate2Flags flags;
     char                  *audit_args;
     char                  *plugin_name;
+    guint64                version_id;
     bool                   is_update2 : 1;
 } UpdateInfo;
 
@@ -1442,53 +1472,7 @@ update_complete(NMSettingsConnection *self, UpdateInfo *info, GError *error)
     g_clear_object(&info->new_settings);
     g_free(info->audit_args);
     g_free(info->plugin_name);
-    g_slice_free(UpdateInfo, info);
-}
-
-static int
-_autoconnect_retries_initial(NMSettingsConnection *self)
-{
-    NMSettingConnection *s_con;
-    int                  retries = -1;
-
-    s_con = nm_connection_get_setting_connection(nm_settings_connection_get_connection(self));
-    if (s_con)
-        retries = nm_setting_connection_get_autoconnect_retries(s_con);
-
-    /* -1 means 'default' */
-    if (retries == -1)
-        retries = nm_config_data_get_autoconnect_retries_default(NM_CONFIG_GET_DATA);
-
-    /* 0 means 'forever', which is translated to a retry count of -1 */
-    if (retries == 0)
-        retries = AUTOCONNECT_RETRIES_FOREVER;
-
-    nm_assert(retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
-    return retries;
-}
-
-static void
-_autoconnect_retries_set(NMSettingsConnection *self, int retries, gboolean is_reset)
-{
-    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
-
-    g_return_if_fail(retries == AUTOCONNECT_RETRIES_FOREVER || retries >= 0);
-
-    if (priv->autoconnect_retries != retries) {
-        _LOGT("autoconnect: retries set %d%s", retries, is_reset ? " (reset)" : "");
-        priv->autoconnect_retries = retries;
-    }
-
-    if (retries)
-        priv->autoconnect_retries_blocked_until = 0;
-    else {
-        /* NOTE: the blocked time must be identical for all connections, otherwise
-         * the tracking of resetting the retry count in NMPolicy needs adjustment
-         * in _connection_autoconnect_retries_set() (as it would need to re-evaluate
-         * the next-timeout every time a connection gets blocked). */
-        priv->autoconnect_retries_blocked_until =
-            nm_utils_get_monotonic_timestamp_sec() + AUTOCONNECT_RESET_RETRIES_TIMER;
-    }
+    nm_g_slice_free(info);
 }
 
 static void
@@ -1502,14 +1486,23 @@ update_auth_cb(NMSettingsConnection  *self,
     UpdateInfo                     *info  = data;
     gs_free_error GError           *local = NULL;
     NMSettingsConnectionPersistMode persist_mode;
+    gs_unref_object NMConnection   *for_agent = NULL;
 
-    if (error) {
-        update_complete(self, info, error);
-        return;
-    }
+    if (error)
+        goto out;
 
     priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
 
+    if (info->version_id != 0 && info->version_id != priv->version_id) {
+        g_set_error_literal(&local,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_VERSION_ID_MISMATCH,
+                            "Update failed because profile changed in the meantime and the "
+                            "version-id mismatches");
+        error = local;
+        goto out;
+    }
+
     if (info->new_settings) {
         if (!_nm_connection_aggregate(info->new_settings,
                                       NM_CONNECTION_AGGREGATE_ANY_SECRETS,
@@ -1539,10 +1532,13 @@ update_auth_cb(NMSettingsConnection  *self,
             /* New secrets, allow autoconnection again */
             if (nm_settings_connection_autoconnect_blocked_reason_set(
                     self,
-                    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS,
+                    NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS,
                     FALSE)
                 && !nm_settings_connection_autoconnect_blocked_reason_get(self))
-                nm_settings_connection_autoconnect_retries_reset(self);
+                nm_manager_devcon_autoconnect_retries_reset(
+                    nm_settings_connection_get_manager(self),
+                    NULL,
+                    self);
         }
     }
 
@@ -1586,10 +1582,9 @@ update_auth_cb(NMSettingsConnection  *self,
              : NM_SETTINGS_CONNECTION_INT_FLAGS_NONE),
         NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED | NM_SETTINGS_CONNECTION_INT_FLAGS_VOLATILE
             | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
-        NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME
-            | (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)
-                   ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE
-                   : NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)
+        (NM_FLAGS_HAS(info->flags, NM_SETTINGS_UPDATE2_FLAG_NO_REAPPLY)
+             ? NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE
+             : NM_SETTINGS_CONNECTION_UPDATE_REASON_REAPPLY_PARTIAL)
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_SYSTEM_SECRETS
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET
@@ -1599,25 +1594,29 @@ update_auth_cb(NMSettingsConnection  *self,
         "update-from-dbus",
         &local);
 
-    if (!local) {
-        gs_unref_object NMConnection *for_agent = NULL;
-
-        /* Dupe the connection so we can clear out non-agent-owned secrets,
-         * as agent-owned secrets are the only ones we send back to be saved.
-         * Only send secrets to agents of the same UID that called update too.
-         */
-        for_agent = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self));
-        _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED);
-        nm_agent_manager_save_secrets(info->agent_mgr,
-                                      nm_dbus_object_get_path(NM_DBUS_OBJECT(self)),
-                                      for_agent,
-                                      info->subject);
+    if (local) {
+        error = local;
+        goto out;
     }
 
+    /* Dupe the connection so we can clear out non-agent-owned secrets,
+     * as agent-owned secrets are the only ones we send back to be saved.
+     * Only send secrets to agents of the same UID that called update too.
+     */
+    for_agent = nm_simple_connection_new_clone(nm_settings_connection_get_connection(self));
+    _nm_connection_clear_secrets_by_secret_flags(for_agent, NM_SETTING_SECRET_FLAG_AGENT_OWNED);
+    nm_agent_manager_save_secrets(info->agent_mgr,
+                                  nm_dbus_object_get_path(NM_DBUS_OBJECT(self)),
+                                  for_agent,
+                                  info->subject);
+
     /* Reset auto retries back to default since connection was updated */
-    nm_settings_connection_autoconnect_retries_reset(self);
+    nm_manager_devcon_autoconnect_retries_reset(nm_settings_connection_get_manager(self),
+                                                NULL,
+                                                self);
 
-    update_complete(self, info, local);
+out:
+    update_complete(self, info, error);
 }
 
 static const char *
@@ -1650,6 +1649,7 @@ settings_connection_update(NMSettingsConnection  *self,
                            GDBusMethodInvocation *context,
                            GVariant              *new_settings,
                            const char            *plugin_name,
+                           guint64                version_id,
                            NMSettingsUpdate2Flags flags)
 {
     NMSettingsConnectionPrivate *priv    = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
@@ -1697,14 +1697,17 @@ settings_connection_update(NMSettingsConnection  *self,
                                              &error))
         goto error;
 
-    info               = g_slice_new0(UpdateInfo);
-    info->is_update2   = is_update2;
-    info->context      = context;
-    info->agent_mgr    = g_object_ref(priv->agent_mgr);
-    info->subject      = subject;
-    info->flags        = flags;
-    info->new_settings = tmp;
-    info->plugin_name  = g_strdup(plugin_name);
+    info  = g_slice_new(UpdateInfo);
+    *info = (UpdateInfo){
+        .is_update2   = is_update2,
+        .context      = context,
+        .agent_mgr    = g_object_ref(priv->agent_mgr),
+        .subject      = subject,
+        .flags        = flags,
+        .new_settings = tmp,
+        .plugin_name  = g_strdup(plugin_name),
+        .version_id   = version_id,
+    };
 
     permission = get_update_modify_permission(nm_settings_connection_get_connection(self),
                                               tmp ?: nm_settings_connection_get_connection(self));
@@ -1738,6 +1741,7 @@ impl_settings_connection_update(NMDBusObject                      *obj,
                                invocation,
                                settings,
                                NULL,
+                               0,
                                NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
 }
 
@@ -1759,6 +1763,7 @@ impl_settings_connection_update_unsaved(NMDBusObject                      *obj,
                                invocation,
                                settings,
                                NULL,
+                               0,
                                NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY);
 }
 
@@ -1778,6 +1783,7 @@ impl_settings_connection_save(NMDBusObject                      *obj,
                                invocation,
                                NULL,
                                NULL,
+                               0,
                                NM_SETTINGS_UPDATE2_FLAG_TO_DISK);
 }
 
@@ -1794,6 +1800,7 @@ impl_settings_connection_update2(NMDBusObject                      *obj,
     gs_unref_variant GVariant *settings    = NULL;
     gs_unref_variant GVariant *args        = NULL;
     gs_free char              *plugin_name = NULL;
+    guint64                    version_id  = 0;
     guint32                    flags_u;
     GError                    *error = NULL;
     GVariantIter               iter;
@@ -1840,6 +1847,11 @@ impl_settings_connection_update2(NMDBusObject                      *obj,
             plugin_name = g_variant_dup_string(args_value, NULL);
             continue;
         }
+        if (nm_streq(args_name, "version-id")
+            && g_variant_is_of_type(args_value, G_VARIANT_TYPE_UINT64)) {
+            version_id = g_variant_get_uint64(args_value);
+            continue;
+        }
 
         error = g_error_new(NM_SETTINGS_ERROR,
                             NM_SETTINGS_ERROR_INVALID_ARGUMENTS,
@@ -1849,7 +1861,7 @@ impl_settings_connection_update2(NMDBusObject                      *obj,
         return;
     }
 
-    settings_connection_update(self, TRUE, invocation, settings, plugin_name, flags);
+    settings_connection_update(self, TRUE, invocation, settings, plugin_name, version_id, flags);
 }
 
 static void
@@ -2542,56 +2554,6 @@ nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self)
 
 /*****************************************************************************/
 
-/**
- * nm_settings_connection_autoconnect_retries_get:
- * @self: the settings connection
- *
- * Returns the number of autoconnect retries left. If the value is
- * not yet set, initialize it with the value from the connection or
- * with the global default.
- */
-int
-nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self)
-{
-    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
-
-    if (G_UNLIKELY(priv->autoconnect_retries == AUTOCONNECT_RETRIES_UNSET)) {
-        _autoconnect_retries_set(self, _autoconnect_retries_initial(self), TRUE);
-    }
-    return priv->autoconnect_retries;
-}
-
-void
-nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries)
-{
-    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
-    g_return_if_fail(retries >= 0);
-
-    _autoconnect_retries_set(self, retries, FALSE);
-}
-
-void
-nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self)
-{
-    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
-
-    _autoconnect_retries_set(self, _autoconnect_retries_initial(self), TRUE);
-}
-
-gint32
-nm_settings_connection_autoconnect_retries_blocked_until(NMSettingsConnection *self)
-{
-    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->autoconnect_retries_blocked_until;
-}
-
-static NM_UTILS_FLAGS2STR_DEFINE(
-    _autoconnect_blocked_reason_to_string,
-    NMSettingsAutoconnectBlockedReason,
-    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE, "none"),
-    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST, "user-request"),
-    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED, "failed"),
-    NM_UTILS_FLAGS2STR(NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS, "no-secrets"), );
-
 NMSettingsAutoconnectBlockedReason
 nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self)
 {
@@ -2599,25 +2561,41 @@ nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self
 }
 
 gboolean
-nm_settings_connection_autoconnect_blocked_reason_set_full(NMSettingsConnection              *self,
-                                                           NMSettingsAutoconnectBlockedReason mask,
-                                                           NMSettingsAutoconnectBlockedReason value)
+nm_settings_connection_autoconnect_blocked_reason_set(NMSettingsConnection              *self,
+                                                      NMSettingsAutoconnectBlockedReason reason,
+                                                      gboolean                           set)
 {
     NMSettingsAutoconnectBlockedReason v;
     NMSettingsConnectionPrivate       *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
-    char                               buf[100];
+    char                               buf1[200];
+    char                               buf2[200];
 
-    nm_assert(mask);
-    nm_assert(!NM_FLAGS_ANY(value, ~mask));
+    nm_assert(reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE);
+    nm_assert(!NM_FLAGS_ANY(reason,
+                            ~(NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST
+                              | NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NO_SECRETS)));
 
     v = priv->autoconnect_blocked_reason;
-    v = (v & ~mask) | (value & mask);
+    v = NM_FLAGS_ASSIGN(v, reason, set);
 
     if (priv->autoconnect_blocked_reason == v)
         return FALSE;
 
-    _LOGT("autoconnect: blocked reason: %s",
-          _autoconnect_blocked_reason_to_string(v, buf, sizeof(buf)));
+    if (set) {
+        _LOGT("block-autoconnect: profile: blocked with reason %s (%s %s)",
+              nm_settings_autoconnect_blocked_reason_to_string(v, buf1, sizeof(buf1)),
+              "just blocked",
+              nm_settings_autoconnect_blocked_reason_to_string(reason, buf2, sizeof(buf2)));
+    } else if (v != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE) {
+        _LOGT("block-autoconnect: profile: blocked with reason %s (%s %s)",
+              nm_settings_autoconnect_blocked_reason_to_string(v, buf1, sizeof(buf1)),
+              "just unblocked",
+              nm_settings_autoconnect_blocked_reason_to_string(reason, buf2, sizeof(buf2)));
+    } else {
+        _LOGT("block-autoconnect: profile: not blocked (unblocked %s)",
+              nm_settings_autoconnect_blocked_reason_to_string(reason, buf1, sizeof(buf1)));
+    }
+
     priv->autoconnect_blocked_reason = v;
     return TRUE;
 }
@@ -2632,9 +2610,7 @@ nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self)
 
     priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
 
-    if (priv->autoconnect_blocked_reason != NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE)
-        return TRUE;
-    if (priv->autoconnect_retries == 0)
+    if (priv->autoconnect_blocked_reason != NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_NONE)
         return TRUE;
 
     flags = priv->flags;
@@ -2693,6 +2669,23 @@ nm_settings_connection_get_uuid(NMSettingsConnection *self)
     return uuid;
 }
 
+guint64
+nm_settings_connection_get_version_id(NMSettingsConnection *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(self), 0);
+
+    return NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->version_id;
+}
+
+void
+nm_settings_connection_bump_version_id(NMSettingsConnection *self)
+{
+    g_return_if_fail(NM_IS_SETTINGS_CONNECTION(self));
+
+    NM_SETTINGS_CONNECTION_GET_PRIVATE(self)->version_id++;
+    _notify(self, PROP_VERSION_ID);
+}
+
 const char *
 nm_settings_connection_get_connection_type(NMSettingsConnection *self)
 {
@@ -2716,9 +2709,13 @@ _nm_settings_connection_cleanup_after_remove(NMSettingsConnection *self)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMSettingsConnection *self = NM_SETTINGS_CONNECTION(object);
+    NMSettingsConnection        *self = NM_SETTINGS_CONNECTION(object);
+    NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
 
     switch (prop_id) {
+    case PROP_VERSION_ID:
+        g_value_set_uint64(value, priv->version_id);
+        break;
     case PROP_UNSAVED:
         g_value_set_boolean(value, nm_settings_connection_get_unsaved(self));
         break;
@@ -2748,6 +2745,7 @@ nm_settings_connection_init(NMSettingsConnection *self)
     self->_priv = priv;
 
     c_list_init(&self->_connections_lst);
+    c_list_init(&self->devcon_con_lst_head);
     c_list_init(&priv->seen_bssids_lst_head);
     c_list_init(&priv->call_ids_lst_head);
     c_list_init(&priv->auth_lst_head);
@@ -2755,7 +2753,7 @@ nm_settings_connection_init(NMSettingsConnection *self)
     priv->agent_mgr = g_object_ref(nm_agent_manager_get());
     priv->settings  = g_object_ref(nm_settings_get());
 
-    priv->autoconnect_retries = AUTOCONNECT_RETRIES_UNSET;
+    priv->version_id = 1;
 }
 
 NMSettingsConnection *
@@ -2776,6 +2774,7 @@ dispose(GObject *object)
     nm_assert(!priv->default_wired_device);
 
     nm_assert(c_list_is_empty(&self->_connections_lst));
+    nm_assert(c_list_is_empty(&self->devcon_con_lst_head));
     nm_assert(c_list_is_empty(&priv->auth_lst_head));
 
     /* Cancel in-progress secrets requests */
@@ -2868,7 +2867,10 @@ static const NMDBusInterfaceInfoExtended interface_info_settings_connection = {
                                                            NM_SETTINGS_CONNECTION_FLAGS),
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Filename",
                                                            "s",
-                                                           NM_SETTINGS_CONNECTION_FILENAME), ), ),
+                                                           NM_SETTINGS_CONNECTION_FILENAME),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("VersionId",
+                                                           "t",
+                                                           NM_SETTINGS_CONNECTION_VERSION_ID), ), ),
 };
 
 static void
@@ -2886,6 +2888,15 @@ nm_settings_connection_class_init(NMSettingsConnectionClass *klass)
     object_class->dispose      = dispose;
     object_class->get_property = get_property;
 
+    obj_properties[PROP_VERSION_ID] =
+        g_param_spec_uint64(NM_SETTINGS_CONNECTION_VERSION_ID,
+                            "",
+                            "",
+                            0,
+                            G_MAXUINT64,
+                            0,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
     obj_properties[PROP_UNSAVED] = g_param_spec_boolean(NM_SETTINGS_CONNECTION_UNSAVED,
                                                         "",
                                                         "",
diff --git a/src/core/settings/nm-settings-connection.h b/src/core/settings/nm-settings-connection.h
index fce7497c..835a978e 100644
--- a/src/core/settings/nm-settings-connection.h
+++ b/src/core/settings/nm-settings-connection.h
@@ -7,8 +7,11 @@
 #ifndef __NETWORKMANAGER_SETTINGS_CONNECTION_H__
 #define __NETWORKMANAGER_SETTINGS_CONNECTION_H__
 
+#include "libnm-core-intern/nm-meta-setting-base.h"
+
 #include "nm-dbus-object.h"
 #include "nm-connection.h"
+#include "NetworkManagerUtils.h"
 
 #include "nm-settings-storage.h"
 
@@ -138,9 +141,10 @@ typedef enum {
 #define NM_SETTINGS_CONNECTION_FLAGS_CHANGED    "flags-changed"
 
 /* Properties */
-#define NM_SETTINGS_CONNECTION_UNSAVED  "unsaved"
-#define NM_SETTINGS_CONNECTION_FLAGS    "flags"
-#define NM_SETTINGS_CONNECTION_FILENAME "filename"
+#define NM_SETTINGS_CONNECTION_UNSAVED    "unsaved"
+#define NM_SETTINGS_CONNECTION_VERSION_ID "version-id"
+#define NM_SETTINGS_CONNECTION_FLAGS      "flags"
+#define NM_SETTINGS_CONNECTION_FILENAME   "filename"
 
 /**
  * NMSettingsConnectionIntFlags:
@@ -188,19 +192,6 @@ typedef enum _NMSettingsConnectionIntFlags {
     _NM_SETTINGS_CONNECTION_INT_FLAGS_ALL = ((_NM_SETTINGS_CONNECTION_INT_FLAGS_LAST - 1) << 1) - 1,
 } NMSettingsConnectionIntFlags;
 
-typedef enum {
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE = 0,
-
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST = (1LL << 0),
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED       = (1LL << 1),
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS   = (1LL << 2),
-
-    NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_ALL =
-        (NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST
-         | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_FAILED
-         | NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NO_SECRETS),
-} NMSettingsAutoconnectBlockedReason;
-
 typedef struct _NMSettingsConnectionCallId NMSettingsConnectionCallId;
 
 typedef struct _NMSettingsConnectionClass NMSettingsConnectionClass;
@@ -210,6 +201,7 @@ struct _NMSettingsConnectionPrivate;
 struct _NMSettingsConnection {
     NMDBusObject                         parent;
     CList                                _connections_lst;
+    CList                                devcon_con_lst_head;
     struct _NMSettingsConnectionPrivate *_priv;
 };
 
@@ -217,7 +209,13 @@ GType nm_settings_connection_get_type(void);
 
 NMSettingsConnection *nm_settings_connection_new(void);
 
+NMSettings *nm_settings_connection_get_settings(NMSettingsConnection *self);
+
+NMManager *nm_settings_connection_get_manager(NMSettingsConnection *self);
+
 NMConnection *nm_settings_connection_get_connection(NMSettingsConnection *self);
+gpointer      nm_settings_connection_get_setting(NMSettingsConnection *self,
+                                                 NMMetaSettingType     meta_type);
 
 void _nm_settings_connection_set_connection(NMSettingsConnection            *self,
                                             NMConnection                    *new_connection,
@@ -234,6 +232,9 @@ const char *nm_settings_connection_get_filename(NMSettingsConnection *self);
 
 guint64 nm_settings_connection_get_last_secret_agent_version_id(NMSettingsConnection *self);
 
+guint64 nm_settings_connection_get_version_id(NMSettingsConnection *self);
+void    nm_settings_connection_bump_version_id(NMSettingsConnection *self);
+
 gboolean
 nm_settings_connection_has_unmodified_applied_connection(NMSettingsConnection *self,
                                                          NMConnection         *applied_connection,
@@ -347,31 +348,15 @@ void nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const cha
 
 guint nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self);
 
-int  nm_settings_connection_autoconnect_retries_get(NMSettingsConnection *self);
-void nm_settings_connection_autoconnect_retries_set(NMSettingsConnection *self, int retries);
-void nm_settings_connection_autoconnect_retries_reset(NMSettingsConnection *self);
-
-gint32 nm_settings_connection_autoconnect_retries_blocked_until(NMSettingsConnection *self);
+gboolean nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self);
 
 NMSettingsAutoconnectBlockedReason
-         nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self);
-gboolean nm_settings_connection_autoconnect_blocked_reason_set_full(
-    NMSettingsConnection              *self,
-    NMSettingsAutoconnectBlockedReason mask,
-    NMSettingsAutoconnectBlockedReason value);
+nm_settings_connection_autoconnect_blocked_reason_get(NMSettingsConnection *self);
 
-static inline gboolean
+gboolean
 nm_settings_connection_autoconnect_blocked_reason_set(NMSettingsConnection              *self,
-                                                      NMSettingsAutoconnectBlockedReason mask,
-                                                      gboolean                           set)
-{
-    return nm_settings_connection_autoconnect_blocked_reason_set_full(
-        self,
-        mask,
-        set ? mask : NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_NONE);
-}
-
-gboolean nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self);
+                                                      NMSettingsAutoconnectBlockedReason reason,
+                                                      gboolean                           set);
 
 const char *nm_settings_connection_get_id(NMSettingsConnection *connection);
 const char *nm_settings_connection_get_uuid(NMSettingsConnection *connection);
diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c
index 9995b490..8796de36 100644
--- a/src/core/settings/nm-settings.c
+++ b/src/core/settings/nm-settings.c
@@ -222,7 +222,7 @@ _sett_conn_entry_get_conn(SettConnEntry *sett_conn_entry)
  * _sett_conn_entry_storage_find_conflicting_storage:
  * @sett_conn_entry: the list of settings-storages for the given UUID.
  * @target_plugin: the settings plugin to check
- * @storage_check_including: (allow-none): optionally compare against this storage.
+ * @storage_check_including: (nullable): optionally compare against this storage.
  * @plugins: the list of plugins sorted in descending priority. This determines
  *   the priority and whether a storage conflicts.
  *
@@ -451,6 +451,16 @@ static void _startup_complete_check(NMSettings *self, gint64 now_msec);
 
 /*****************************************************************************/
 
+NMManager *
+nm_settings_get_manager(NMSettings *self)
+{
+    g_return_val_if_fail(NM_IS_SETTINGS(self), NULL);
+
+    return NM_SETTINGS_GET_PRIVATE(self)->manager;
+}
+
+/*****************************************************************************/
+
 static void
 _emit_connection_added(NMSettings *self, NMSettingsConnection *sett_conn)
 {
@@ -525,7 +535,7 @@ _startup_complete_check_is_ready(NMSettings           *self,
         /* Check that device is compatible with the device. We are also happy
          * with a device compatible but for which the connection is disallowed
          * by NM configuration. */
-        if (!nm_device_check_connection_compatible(device, conn, &error)
+        if (!nm_device_check_connection_compatible(device, conn, TRUE, &error)
             && !g_error_matches(error,
                                 NM_UTILS_ERROR,
                                 NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED))
@@ -1080,11 +1090,13 @@ _connection_changed_update(NMSettings                      *self,
 
     is_new = c_list_is_empty(&sett_conn->_connections_lst);
 
-    _LOGT("update[%s]: %s connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT ")",
+    _LOGT("update[%s]: %s connection \"%s\" (" NM_SETTINGS_STORAGE_PRINT_FMT "), "
+          "new version-id %" G_GUINT64_FORMAT,
           nm_settings_storage_get_uuid(storage),
           is_new ? "adding" : "updating",
           nm_connection_get_id(connection),
-          NM_SETTINGS_STORAGE_PRINT_ARG(storage));
+          NM_SETTINGS_STORAGE_PRINT_ARG(storage),
+          (nm_settings_connection_get_version_id(sett_conn) + 1u));
 
     _nm_settings_connection_set_storage(sett_conn, storage);
 
@@ -1109,7 +1121,7 @@ _connection_changed_update(NMSettings                      *self,
     if (NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_BLOCK_AUTOCONNECT)) {
         nm_settings_connection_autoconnect_blocked_reason_set(
             sett_conn,
-            NM_SETTINGS_AUTO_CONNECT_BLOCKED_REASON_USER_REQUEST,
+            NM_SETTINGS_AUTOCONNECT_BLOCKED_REASON_USER_REQUEST,
             TRUE);
     }
 
@@ -1156,6 +1168,8 @@ _connection_changed_update(NMSettings                      *self,
                                      path);
     }
 
+    nm_settings_connection_bump_version_id(sett_conn);
+
     if (is_new) {
         nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self),
                                    &interface_info_settings,
@@ -1237,6 +1251,8 @@ _connection_changed_delete(NMSettings           *self,
                                          | NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL,
                                      FALSE);
 
+    nm_manager_notify_delete_settings_connections(priv->manager, sett_conn);
+
     _emit_connection_removed(self, sett_conn);
 
     _nm_settings_connection_cleanup_after_remove(sett_conn);
@@ -1453,10 +1469,16 @@ static void
 _plugin_connections_reload(NMSettings *self)
 {
     NMSettingsPrivate *priv = NM_SETTINGS_GET_PRIVATE(self);
-    GSList            *iter;
-
-    for (iter = priv->plugins; iter; iter = iter->next) {
-        nm_settings_plugin_reload_connections(iter->data, _plugin_connections_reload_cb, self);
+    GSList            *iter_plugin;
+    GHashTableIter     iter_entry;
+    SettConnEntry     *entry;
+    gboolean           warned = FALSE;
+    gboolean           migrate;
+
+    for (iter_plugin = priv->plugins; iter_plugin; iter_plugin = iter_plugin->next) {
+        nm_settings_plugin_reload_connections(iter_plugin->data,
+                                              _plugin_connections_reload_cb,
+                                              self);
     }
 
     _connection_changed_process_all_dirty(
@@ -1469,8 +1491,53 @@ _plugin_connections_reload(NMSettings *self)
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_RESET_AGENT_SECRETS
             | NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET);
 
-    for (iter = priv->plugins; iter; iter = iter->next)
-        nm_settings_plugin_load_connections_done(iter->data);
+    for (iter_plugin = priv->plugins; iter_plugin; iter_plugin = iter_plugin->next)
+        nm_settings_plugin_load_connections_done(iter_plugin->data);
+
+    migrate = nm_config_data_get_value_boolean(nm_config_get_data(priv->config),
+                                               NM_CONFIG_KEYFILE_GROUP_MAIN,
+                                               NM_CONFIG_KEYFILE_KEY_MAIN_MIGRATE_IFCFG_RH,
+                                               NM_CONFIG_DEFAULT_MAIN_MIGRATE_IFCFG_RH_BOOL);
+
+    g_hash_table_iter_init(&iter_entry, priv->sce_idx);
+    while (g_hash_table_iter_next(&iter_entry, (gpointer *) &entry, NULL)) {
+        const char *plugin;
+
+        plugin = nm_settings_plugin_get_plugin_name(nm_settings_storage_get_plugin(entry->storage));
+
+        if (nm_streq0(plugin, "ifcfg-rh")) {
+            if (!warned) {
+                if (migrate) {
+                    nm_log_warn(
+                        LOGD_SETTINGS,
+                        "Warning: connections were found in ifcfg-rh format and the "
+                        "\"main.migrate-ifcfg-rh\" option is enabled. Those connections will be "
+                        "migrated to keyfile. To convert them back, disable the option and then "
+                        "run \"nmcli connection migrate --plugin ifcfg-rh $UUID\"");
+                } else {
+                    nm_log_info(
+                        LOGD_SETTINGS,
+                        "Warning: the ifcfg-rh plugin is deprecated, please migrate connections "
+                        "to the keyfile format using \"nmcli connection migrate\"");
+                }
+                warned = TRUE;
+            }
+            if (migrate) {
+                _LOGW("migrating connection %s ('%s') from ifcfg-rh to keyfile",
+                      entry->uuid,
+                      nm_settings_connection_get_id(entry->sett_conn));
+                nm_settings_connection_update(entry->sett_conn,
+                                              "keyfile",
+                                              NULL,
+                                              NM_SETTINGS_CONNECTION_PERSIST_MODE_KEEP,
+                                              NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                              NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                              NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
+                                              "migrate-ifcfg-rh",
+                                              NULL);
+            }
+        }
+    }
 }
 
 /*****************************************************************************/
@@ -1728,7 +1795,8 @@ _set_nmmeta_tombstone(NMSettings *self,
  * @persist_mode: the persist-mode for this profile.
  * @add_reason: the add-reason flags.
  * @sett_flags: the settings flags to set.
- * @out_sett_conn: (allow-none) (transfer none): the added settings connection on success.
+ * @out_sett_conn: (out) (optional) (nullable) (transfer none): the added
+ *   settings connection on success.
  * @error: on return, a location to store any errors that may occur
  *
  * Creates a new #NMSettingsConnection for the given source @connection.
@@ -2000,9 +2068,10 @@ nm_settings_update_connection(NMSettings                      *self,
     gs_unref_object NMConnection      *new_connection_cloned = NULL;
     gs_unref_object NMConnection      *new_connection        = NULL;
     NMConnection                      *new_connection_real;
-    gs_unref_object NMSettingsStorage *cur_storage  = NULL;
-    gs_unref_object NMSettingsStorage *new_storage  = NULL;
-    NMSettingsStorage                 *drop_storage = NULL;
+    gs_unref_object NMSettingsStorage *cur_storage         = NULL;
+    gs_unref_object NMSettingsStorage *new_storage         = NULL;
+    NMSettingsStorage                 *drop_storage        = NULL;
+    NMSettingsStorage                 *prev_update_storage = NULL;
     SettConnEntry                     *sett_conn_entry;
     gboolean                           cur_in_memory;
     gboolean                           new_in_memory;
@@ -2246,16 +2315,17 @@ nm_settings_update_connection(NMSettings                      *self,
                                                       drop_storage,
                                                       &local);
         } else {
-            success = _update_connection_to_plugin(self,
-                                                   update_storage,
-                                                   connection,
-                                                   new_flags,
-                                                   update_reason,
-                                                   new_shadowed_storage_filename,
-                                                   new_shadowed_owned,
-                                                   &new_storage,
-                                                   &new_connection,
-                                                   &local);
+            success = _update_connection_to_plugin(
+                self,
+                update_storage,
+                connection,
+                new_flags,
+                NM_FLAGS_HAS(update_reason, NM_SETTINGS_CONNECTION_UPDATE_REASON_FORCE_RENAME),
+                new_shadowed_storage_filename,
+                new_shadowed_owned,
+                &new_storage,
+                &new_connection,
+                &local);
         }
         if (!success) {
             gboolean ignore_failure;
@@ -2306,6 +2376,9 @@ nm_settings_update_connection(NMSettings                      *self,
                 nm_assert_not_reached();
                 new_connection_real = new_connection;
             }
+
+            if (update_storage && new_storage != update_storage)
+                prev_update_storage = update_storage;
         }
     }
 
@@ -2314,6 +2387,12 @@ nm_settings_update_connection(NMSettings                      *self,
 
     _connection_changed_track(self, new_storage, new_connection_real, TRUE);
 
+    if (prev_update_storage) {
+        /* The storage was swapped by the update call. The old one needs
+         * to be dropped, which we do by setting the connection to NULL. */
+        _connection_changed_track(self, prev_update_storage, NULL, FALSE);
+    }
+
     if (drop_storage && drop_storage != new_storage) {
         gs_free_error GError *local = NULL;
 
@@ -3102,7 +3181,7 @@ error:
 /**
  * nm_settings_get_connections:
  * @self: the #NMSettings
- * @out_len: (out) (allow-none): returns the number of returned
+ * @out_len: (out) (optional): returns the number of returned
  *   connections.
  *
  * Returns: (transfer none): a list of NMSettingsConnections. The list is
@@ -3205,10 +3284,10 @@ nm_settings_get_connections_sorted_by_autoconnect_priority(NMSettings *self, gui
 /**
  * nm_settings_get_connections_clone:
  * @self: the #NMSetting
- * @out_len: (allow-none): optional output argument
+ * @out_len: (optional): optional output argument
  * @func: caller-supplied function for filtering connections
  * @func_data: caller-supplied data passed to @func
- * @sort_compare_func: (allow-none): optional function pointer for
+ * @sort_compare_func: (nullable): optional function pointer for
  *   sorting the returned list.
  * @sort_data: user data for @sort_compare_func.
  *
@@ -3629,7 +3708,7 @@ have_connection_for_device(NMSettings *self, NMDevice *device)
     c_list_for_each_entry (sett_conn, &priv->connections_lst_head, _connections_lst) {
         NMConnection *connection = nm_settings_connection_get_connection(sett_conn);
 
-        if (!nm_device_check_connection_compatible(device, connection, NULL))
+        if (!nm_device_check_connection_compatible(device, connection, TRUE, NULL))
             continue;
 
         if (nm_settings_connection_default_wired_get_device(sett_conn))
diff --git a/src/core/settings/nm-settings.h b/src/core/settings/nm-settings.h
index aba3c565..020623d0 100644
--- a/src/core/settings/nm-settings.h
+++ b/src/core/settings/nm-settings.h
@@ -58,6 +58,8 @@ NMSettings *nm_settings_get(void);
 
 NMSettings *nm_settings_new(NMManager *manager);
 
+NMManager *nm_settings_get_manager(NMSettings *self);
+
 gboolean nm_settings_start(NMSettings *self, GError **error);
 
 typedef void (*NMSettingsAddCallback)(NMSettings            *settings,
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index eb0d733d..0a385247 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -52,7 +52,6 @@ typedef struct {
 
     GHashTable *unmanaged_specs;
     GHashTable *unrecognized_specs;
-
 } NMSIfcfgRHPluginPrivate;
 
 struct _NMSIfcfgRHPlugin {
@@ -177,6 +176,7 @@ nm_assert_self(NMSIfcfgRHPlugin *self, gboolean unhandled_specs_consistent)
 static NMSIfcfgRHStorage *
 _load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error)
 {
+    NMSIfcfgRHStorage            *ret            = NULL;
     gs_unref_object NMConnection *connection     = NULL;
     gs_free_error GError         *load_error     = NULL;
     gs_free char                 *unhandled_spec = NULL;
@@ -224,16 +224,16 @@ _load_file(NMSIfcfgRHPlugin *self, const char *filename, GError **error)
             nm_assert_not_reached();
             return NULL;
         }
-        return nms_ifcfg_rh_storage_new_unhandled(self,
+
+        ret = nms_ifcfg_rh_storage_new_unhandled(self, filename, unmanaged_spec, unrecognized_spec);
+    } else {
+        ret = nms_ifcfg_rh_storage_new_connection(self,
                                                   filename,
-                                                  unmanaged_spec,
-                                                  unrecognized_spec);
+                                                  g_steal_pointer(&connection),
+                                                  &st.st_mtim);
     }
 
-    return nms_ifcfg_rh_storage_new_connection(self,
-                                               filename,
-                                               g_steal_pointer(&connection),
-                                               &st.st_mtim);
+    return ret;
 }
 
 static void
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 6cfb5705..84a9479d 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -917,7 +917,7 @@ enum {
  * @options_route: (in-out): when line is from the OPTIONS setting, this is a pre-created
  *   route object that is completed with the settings from options. Otherwise,
  *   it shall point to %NULL and a new route is created and returned.
- * @out_route: (out) (transfer-full) (allow-none): the parsed %NMIPRoute instance.
+ * @out_route: (out) (transfer full) (optional): the parsed %NMIPRoute instance.
  *   In case a @options_route is passed in, it returns the input route that was modified
  *   in-place. But the caller must unref the returned route in either case.
  * @error: the failure description.
@@ -2481,6 +2481,11 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
         g_object_set(s_ip6, NM_SETTING_IP_CONFIG_DHCP_IAID, v, NULL);
 
     nm_clear_g_free(&value);
+    v = svGetValueStr(ifcfg, "DHCPV6_PD_HINT", &value);
+    if (v)
+        g_object_set(s_ip6, NM_SETTING_IP6_CONFIG_DHCP_PD_HINT, v, NULL);
+
+    nm_clear_g_free(&value);
     v = svGetValueStr(ifcfg, "DHCPV6_HOSTNAME", &value);
     /* Use DHCP_HOSTNAME as fallback if it is in FQDN format and ipv6.method is
      * auto or dhcp: this is required to support old ifcfg files
@@ -2591,7 +2596,7 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
                         &local)) {
         PARSE_WARNING("%s", local->message);
         g_clear_error(&local);
-    } else if (errno == ENOENT) {
+    } else if (errno == ENOKEY) {
         /* The key is not specified. If "v" (IPV6_TOKEN) is set,
          * we default to EUI64. Otherwise, the connection would not verify. */
         if (v)
@@ -2683,16 +2688,25 @@ make_hostname_setting(shvarFile *ifcfg)
     NMTernary  from_dns_lookup;
     NMTernary  only_from_default;
     int        priority;
+    gboolean   has_setting = FALSE;
 
     priority = svGetValueInt64(ifcfg, "HOSTNAME_PRIORITY", 10, G_MININT32, G_MAXINT32, 0);
+    if (!has_setting && errno != ENOKEY)
+        has_setting = TRUE;
+
+    from_dhcp = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DHCP");
+    if (!has_setting && errno != ENOKEY)
+        has_setting = TRUE;
+
+    from_dns_lookup = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP");
+    if (!has_setting && errno != ENOKEY)
+        has_setting = TRUE;
 
-    from_dhcp         = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DHCP");
-    from_dns_lookup   = svGetValueTernary(ifcfg, "HOSTNAME_FROM_DNS_LOOKUP");
     only_from_default = svGetValueTernary(ifcfg, "HOSTNAME_ONLY_FROM_DEFAULT");
+    if (!has_setting && errno != ENOKEY)
+        has_setting = TRUE;
 
-    /* Create the setting when at least one key is not default*/
-    if (priority == 0 && from_dhcp == NM_TERNARY_DEFAULT && from_dns_lookup == NM_TERNARY_DEFAULT
-        && only_from_default == NM_TERNARY_DEFAULT)
+    if (!has_setting)
         return NULL;
 
     setting = nm_setting_hostname_new();
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index 207b8700..50e352d3 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -881,6 +881,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
     _KEY_TYPE("DHCPV6_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCPV6_HOSTNAME_FLAGS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCPV6_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("DHCPV6_PD_HINT", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCPV6_SEND_HOSTNAME", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCP_CLIENT_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCP_FQDN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index 51b118e3..eb9e418a 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -33,7 +33,7 @@ typedef struct {
     NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[263];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[264];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx);
 
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index 97637063..08deaf5a 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -62,6 +62,24 @@
 /*****************************************************************************/
 
 static void
+set_error_unsupported(GError      **error,
+                      NMConnection *connection,
+                      const char   *name,
+                      gboolean      is_setting)
+{
+    g_set_error(error,
+                NM_SETTINGS_ERROR,
+                NM_SETTINGS_ERROR_NOT_SUPPORTED_BY_PLUGIN,
+                "The ifcfg-rh plugin doesn't support %s '%s'. If you are modifying an existing "
+                "connection profile saved in ifcfg-rh format, please migrate the connection to "
+                "keyfile using 'nmcli connection migrate %s' or via the Update2() D-Bus API "
+                "and try again.",
+                is_setting ? "setting" : "property",
+                name,
+                nm_connection_get_uuid(connection));
+};
+
+static void
 save_secret_flags(shvarFile *ifcfg, const char *key, NMSettingSecretFlags flags)
 {
     GString *str;
@@ -3100,6 +3118,9 @@ write_ip6_setting(NMConnection *connection, shvarFile *ifcfg, GString **out_rout
                   "DHCPV6_DUID",
                   nm_setting_ip6_config_get_dhcp_duid(NM_SETTING_IP6_CONFIG(s_ip6)));
     svSetValueStr(ifcfg, "DHCPV6_IAID", nm_setting_ip_config_get_dhcp_iaid(s_ip6));
+    svSetValueStr(ifcfg,
+                  "DHCPV6_PD_HINT",
+                  nm_setting_ip6_config_get_dhcp_pd_hint(NM_SETTING_IP6_CONFIG(s_ip6)));
 
     hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip6);
     svSetValueStr(ifcfg, "DHCPV6_HOSTNAME", hostname);
@@ -3485,6 +3506,11 @@ do_write_construct(NMConnection                   *connection,
     write_sriov_setting(connection, ifcfg);
     write_tc_setting(connection, ifcfg);
 
+    if (_nm_connection_get_setting(connection, NM_TYPE_SETTING_LINK)) {
+        set_error_unsupported(error, connection, "link", TRUE);
+        return FALSE;
+    }
+
     route_path_is_svformat = utils_has_route_file_new_syntax(route_path);
 
     has_complex_routes_v4 = utils_has_complex_routes(ifcfg_name, AF_INET);
diff --git a/src/core/settings/plugins/ifcfg-rh/shvar.c b/src/core/settings/plugins/ifcfg-rh/shvar.c
index fe8187c3..1ca2ea60 100644
--- a/src/core/settings/plugins/ifcfg-rh/shvar.c
+++ b/src/core/settings/plugins/ifcfg-rh/shvar.c
@@ -97,23 +97,32 @@ static void _line_link_parse(shvarFile *s, const char *value, gsize len);
  * in case no valid value is found, the fallback value. Valid values
  * are: "yes", "true", "t", "y", "1" and "no", "false", "f", "n", "0".
  *
+ * Always sets errno. Either to zero on success, to ENOKEY for NULL
+ * or to EINVAL otherwise.
+ *
  * Returns: the parsed boolean value or @fallback.
  */
 int
 svParseBoolean(const char *value, int fallback)
 {
-    if (!value)
+    if (!value) {
+        errno = ENOKEY;
         return fallback;
+    }
 
     if (!g_ascii_strcasecmp("yes", value) || !g_ascii_strcasecmp("true", value)
         || !g_ascii_strcasecmp("t", value) || !g_ascii_strcasecmp("y", value)
-        || !g_ascii_strcasecmp("1", value))
+        || !g_ascii_strcasecmp("1", value)) {
+        errno = 0;
         return TRUE;
-    else if (!g_ascii_strcasecmp("no", value) || !g_ascii_strcasecmp("false", value)
-             || !g_ascii_strcasecmp("f", value) || !g_ascii_strcasecmp("n", value)
-             || !g_ascii_strcasecmp("0", value))
+    } else if (!g_ascii_strcasecmp("no", value) || !g_ascii_strcasecmp("false", value)
+               || !g_ascii_strcasecmp("f", value) || !g_ascii_strcasecmp("n", value)
+               || !g_ascii_strcasecmp("0", value)) {
+        errno = 0;
         return FALSE;
+    }
 
+    errno = EINVAL;
     return fallback;
 }
 
@@ -1253,6 +1262,7 @@ svGetValueStr_cp(shvarFile *s, const char *key)
  * @fallback: the fallback value in any error case
  *
  * Reads a value @key and converts it to a boolean using svParseBoolean().
+ * This always sets errno, see svParseBoolean().
  *
  * Returns: the parsed boolean value or @fallback.
  */
@@ -1271,6 +1281,7 @@ svGetValueBoolean(shvarFile *s, const char *key, int fallback)
  * @key: the name of the key to read
  *
  * Reads a value @key and converts it to a NMTernary value.
+ * This always sets errno, see svParseBoolean().
  *
  * Returns: the parsed NMTernary
  */
@@ -1328,7 +1339,7 @@ svGetValueEnum(shvarFile *s, const char *key, GType gtype, int *out_value, GErro
     if (!svalue) {
         /* don't touch out_value. The caller is supposed
          * to initialize it with the default value. */
-        errno = ENOENT;
+        errno = ENOKEY;
         return TRUE;
     }
 
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
index 1d7de8d2..1679cab6 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -891,6 +891,7 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
     gboolean                      reread_same;
     const char                   *uuid;
     char                          strbuf[100];
+    NMTernary                     force_rename2;
 
     _nm_assert_storage(self, storage, TRUE);
     nm_assert(NM_IS_CONNECTION(connection));
@@ -910,6 +911,20 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
     previous_filename = nms_keyfile_storage_get_filename(storage);
     uuid              = nms_keyfile_storage_get_uuid(storage);
 
+    if (force_rename)
+        force_rename2 = NM_TERNARY_TRUE;
+    else {
+        /* If the caller does not force a rename, we honor [keyfile].rename
+         * setting, and (if enabled) we rename by following the preferred name
+         * as necessary.  That's indicated with NM_TERNARY_DEFAULT. */
+        force_rename2 = nm_config_data_get_value_boolean(NM_CONFIG_GET_DATA,
+                                                         NM_CONFIG_KEYFILE_GROUP_KEYFILE,
+                                                         NM_CONFIG_KEYFILE_KEY_KEYFILE_RENAME,
+                                                         FALSE)
+                            ? NM_TERNARY_DEFAULT
+                            : NM_TERNARY_FALSE;
+    }
+
     if (!nms_keyfile_writer_connection(
             connection,
             is_nm_generated,
@@ -922,7 +937,7 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
             _get_plugin_dir(priv),
             previous_filename,
             FALSE,
-            FALSE,
+            force_rename2,
             nm_sett_util_allow_filename_cb,
             NM_SETT_UTIL_ALLOW_FILENAME_DATA(&priv->storages, previous_filename),
             &full_filename,
@@ -938,7 +953,8 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
         return FALSE;
     }
 
-    nm_assert(full_filename && nm_streq(full_filename, previous_filename));
+    nm_assert(full_filename);
+    nm_assert(force_rename2 != NM_TERNARY_FALSE || nm_streq(full_filename, previous_filename));
 
     if (!reread || reread_same)
         nm_g_object_ref_set(&reread, connection);
@@ -957,11 +973,33 @@ nms_keyfile_plugin_update_connection(NMSKeyfilePlugin   *self,
                               "\")",
                               ""));
 
-    storage->u.conn_data.is_nm_generated = is_nm_generated;
-    storage->u.conn_data.is_volatile     = is_volatile;
-    storage->u.conn_data.is_external     = is_external;
-    storage->u.conn_data.stat_mtime      = *nm_sett_util_stat_mtime(full_filename, FALSE, &mtime);
-    storage->u.conn_data.shadowed_owned  = shadowed_owned;
+    nm_sett_util_stat_mtime(full_filename, FALSE, &mtime);
+
+    if (nm_streq(full_filename, previous_filename)) {
+        storage->u.conn_data.is_nm_generated = is_nm_generated;
+        storage->u.conn_data.is_volatile     = is_volatile;
+        storage->u.conn_data.is_external     = is_external;
+        storage->u.conn_data.stat_mtime      = mtime;
+        storage->u.conn_data.shadowed_owned  = shadowed_owned;
+    } else {
+        NMSKeyfileStorage *storage_new;
+
+        /* The filename changed. We cannot modify the filename of an NMSettingsStorage.
+         * We need to create a new one. */
+        storage_new =
+            nms_keyfile_storage_new_connection(NMS_KEYFILE_PLUGIN(storage->parent._plugin),
+                                               g_object_ref(reread),
+                                               full_filename,
+                                               storage->storage_type,
+                                               is_nm_generated,
+                                               is_volatile,
+                                               is_external,
+                                               storage->u.conn_data.shadowed_storage,
+                                               shadowed_owned,
+                                               &mtime);
+        nm_sett_util_storages_add_take(&priv->storages, storage_new);
+        storage = storage_new;
+    }
 
     *out_storage    = g_object_ref(NM_SETTINGS_STORAGE(storage));
     *out_connection = g_steal_pointer(&reread);
@@ -1066,12 +1104,12 @@ delete_connection(NMSettingsPlugin *plugin, NMSettingsStorage *storage_x, GError
  * @shadowed_storage: a tombstone can also shadow an existing storage.
  *   In combination with @set and @in_memory, this is allowed to store
  *   the shadowed storage filename.
- * @out_storage: (transfer full) (allow-none): the storage element that changes, or
- *   NULL if nothing changed. Note that the file on disk is already as
- *   we want to write it, then this still counts as a change. No change only
+ * @out_storage: (transfer full) (optional) (nullable): the storage element that
+ *   changes, or %NULL if nothing changed. Note that the file on disk is already
+ *   as we want to write it, then this still counts as a change. No change only
  *   means if we try to delete a storage (@set %FALSE) that did not
  *   exist previously.
- * @out_hard_failure: (allow-none): on failure, indicate that this is a hard failure.
+ * @out_hard_failure: (optional): on failure, indicate that this is a hard failure.
  *
  * The function writes or deletes nmmeta files to/from filesystem. In this case,
  * the nmmeta files can only be symlinks to /dev/null (to indicate tombstones).
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
index ad6f277c..b1dd2e44 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.c
@@ -195,7 +195,7 @@ _internal_write_connection(NMConnection                   *connection,
                            pid_t                           owner_grp,
                            const char                     *existing_path,
                            gboolean                        existing_path_read_only,
-                           gboolean                        force_rename,
+                           NMTernary                       force_rename,
                            NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
                            gpointer                        allow_filename_user_data,
                            char                          **out_path,
@@ -212,6 +212,7 @@ _internal_write_connection(NMConnection                   *connection,
     gs_free_error GError           *local_err = NULL;
     int                             errsv;
     gboolean                        rename;
+    gboolean                        rename_follow;
     int                             i_path;
     gs_unref_object NMConnection   *reread      = NULL;
     gboolean                        reread_same = FALSE;
@@ -223,8 +224,12 @@ _internal_write_connection(NMConnection                   *connection,
 
     nm_assert(!shadowed_owned || shadowed_storage);
 
-    rename = force_rename || existing_path_read_only
-             || (existing_path && !nm_utils_file_is_in_path(existing_path, keyfile_dir));
+    rename = existing_path_read_only
+             || (existing_path && !nm_utils_file_is_in_path(existing_path, keyfile_dir))
+             || force_rename == NM_TERNARY_TRUE;
+
+    /* Follow the connection.id upon change. */
+    rename_follow = !rename && existing_path && force_rename == NM_TERNARY_DEFAULT;
 
     id = nm_connection_get_id(connection);
     nm_assert(id && *id);
@@ -283,7 +288,7 @@ _internal_write_connection(NMConnection                   *connection,
         gboolean      is_existing_path;
 
         if (i_path == -2) {
-            if (!existing_path || rename)
+            if (!existing_path || rename || rename_follow)
                 continue;
             path_candidate = g_strdup(existing_path);
         } else if (i_path == -1) {
@@ -427,7 +432,7 @@ nms_keyfile_writer_connection(NMConnection                   *connection,
                               const char                     *profile_dir,
                               const char                     *existing_path,
                               gboolean                        existing_path_read_only,
-                              gboolean                        force_rename,
+                              NMTernary                       force_rename,
                               NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
                               gpointer                        allow_filename_user_data,
                               char                          **out_path,
@@ -458,14 +463,14 @@ nms_keyfile_writer_connection(NMConnection                   *connection,
 }
 
 gboolean
-nms_keyfile_writer_test_connection(NMConnection  *connection,
-                                   const char    *keyfile_dir,
-                                   uid_t          owner_uid,
-                                   pid_t          owner_grp,
-                                   char         **out_path,
-                                   NMConnection **out_reread,
-                                   gboolean      *out_reread_same,
-                                   GError       **error)
+nmtst_keyfile_writer_test_connection(NMConnection  *connection,
+                                     const char    *keyfile_dir,
+                                     uid_t          owner_uid,
+                                     pid_t          owner_grp,
+                                     char         **out_path,
+                                     NMConnection **out_reread,
+                                     gboolean      *out_reread_same,
+                                     GError       **error)
 {
     return _internal_write_connection(connection,
                                       FALSE,
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
index 62aaa19d..850d5522 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-writer.h
@@ -22,7 +22,7 @@ gboolean nms_keyfile_writer_connection(NMConnection                   *connectio
                                        const char                     *profile_dir,
                                        const char                     *existing_path,
                                        gboolean                        existing_path_read_only,
-                                       gboolean                        force_rename,
+                                       NMTernary                       force_rename,
                                        NMSKeyfileWriterAllowFilenameCb allow_filename_cb,
                                        gpointer                        allow_filename_user_data,
                                        char                          **out_path,
@@ -30,13 +30,13 @@ gboolean nms_keyfile_writer_connection(NMConnection                   *connectio
                                        gboolean                       *out_reread_same,
                                        GError                        **error);
 
-gboolean nms_keyfile_writer_test_connection(NMConnection  *connection,
-                                            const char    *keyfile_dir,
-                                            uid_t          owner_uid,
-                                            pid_t          owner_grp,
-                                            char         **out_path,
-                                            NMConnection **out_reread,
-                                            gboolean      *out_reread_same,
-                                            GError       **error);
+gboolean nmtst_keyfile_writer_test_connection(NMConnection  *connection,
+                                              const char    *keyfile_dir,
+                                              uid_t          owner_uid,
+                                              pid_t          owner_grp,
+                                              char         **out_path,
+                                              NMConnection **out_reread,
+                                              gboolean      *out_reread_same,
+                                              GError       **error);
 
 #endif /* __NMS_KEYFILE_WRITER_H__ */
diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
index 83019bab..866b1ffd 100644
--- a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
+++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -138,14 +138,14 @@ write_test_connection_reread(NMConnection  *connection,
 
     connection_normalized = nmtst_connection_duplicate_and_normalize(connection);
 
-    success = nms_keyfile_writer_test_connection(connection_normalized,
-                                                 TEST_SCRATCH_DIR,
-                                                 owner_uid,
-                                                 owner_grp,
-                                                 testfile,
-                                                 out_reread,
-                                                 out_reread_same,
-                                                 p_error);
+    success = nmtst_keyfile_writer_test_connection(connection_normalized,
+                                                   TEST_SCRATCH_DIR,
+                                                   owner_uid,
+                                                   owner_grp,
+                                                   testfile,
+                                                   out_reread,
+                                                   out_reread_same,
+                                                   p_error);
     g_assert_no_error(error);
     g_assert(success);
     g_assert(*testfile && (*testfile)[0]);
diff --git a/src/core/supplicant/nm-supplicant-manager.c b/src/core/supplicant/nm-supplicant-manager.c
index f6927500..3b805693 100644
--- a/src/core/supplicant/nm-supplicant-manager.c
+++ b/src/core/supplicant/nm-supplicant-manager.c
@@ -447,7 +447,7 @@ _create_iface_dbus_call_get_interface_cb(GObject *source, GAsyncResult *result,
     nm_assert(handle->name_owner == priv->name_owner);
 
     if (!res) {
-        char ifname[NMP_IFNAMSIZ];
+        char ifname[NM_IFNAMSIZ];
 
         if (handle->create_iface_try_count < CREATE_IFACE_TRY_COUNT_MAX
             && nm_dbus_error_is(error, NM_WPAS_ERROR_UNKNOWN_IFACE)
@@ -489,7 +489,7 @@ _create_iface_dbus_call_create_interface_cb(GObject      *source,
     gs_unref_variant GVariant   *res   = NULL;
     gs_free_error GError        *error = NULL;
     const char                  *iface_path_str;
-    char                         ifname[NMP_IFNAMSIZ];
+    char                         ifname[NM_IFNAMSIZ];
 
     res = g_dbus_connection_call_finish(dbus_connection, result, &error);
 
@@ -619,7 +619,7 @@ static void
 _create_iface_dbus_start(NMSupplicantManager *self, NMSupplMgrCreateIfaceHandle *handle)
 {
     NMSupplicantManagerPrivate *priv = NM_SUPPLICANT_MANAGER_GET_PRIVATE(self);
-    char                        ifname[NMP_IFNAMSIZ];
+    char                        ifname[NM_IFNAMSIZ];
 
     nm_assert(priv->name_owner);
     nm_assert(!handle->cancellable);
diff --git a/src/core/tests/test-core-with-expect.c b/src/core/tests/test-core-with-expect.c
index b05144ac..01510126 100644
--- a/src/core/tests/test-core-with-expect.c
+++ b/src/core/tests/test-core-with-expect.c
@@ -333,9 +333,8 @@ do_test_nm_utils_kill_child(void)
 
     /* pid3s should not be a valid process, hence the call should fail. Note, that there
      * is a race here. */
-    NMTST_EXPECT_NM_ERROR(
-        "kill child process 'test-s-3-2' (*): failed due to unexpected return value -1 by waitpid "
-        "(No child process*, 10) after sending no signal (0)");
+    NMTST_EXPECT_NM_ERROR("kill child process 'test-s-3-2' (*): unexpected error while waitpid: No "
+                          "child process* (10)");
     test_nm_utils_kill_child_sync_do("test-s-3-2", pid3s, 0, 0, FALSE, NULL);
 
     NMTST_EXPECT_NM_DEBUG("kill child process 'test-s-4' (*): waiting up to 50 milliseconds for "
@@ -396,9 +395,8 @@ do_test_nm_utils_kill_child(void)
 
     /* pid3a should not be a valid process, hence the call should fail. Note, that there
      * is a race here. */
-    NMTST_EXPECT_NM_ERROR(
-        "kill child process 'test-a-3-2' (*): failed due to unexpected return value -1 by waitpid "
-        "(No child process*, 10) after sending no signal (0)");
+    NMTST_EXPECT_NM_ERROR("kill child process 'test-a-3-2' (*): unexpected error while "
+                          "waitpid: No child process* (10)");
     NMTST_EXPECT_NM_DEBUG(
         "kill child process 'test-a-3-2' (*): invoke callback: killing child failed");
     test_nm_utils_kill_child_async_do("test-a-3-2", pid3a, 0, 0, FALSE, NULL);
diff --git a/src/core/tests/test-core.c b/src/core/tests/test-core.c
index 887803bf..8296d745 100644
--- a/src/core/tests/test-core.c
+++ b/src/core/tests/test-core.c
@@ -18,6 +18,7 @@
 
 #include "dns/nm-dns-manager.h"
 #include "nm-connectivity.h"
+#include "nm-firewall-utils.h"
 
 #include "nm-test-utils-core.h"
 
@@ -1243,13 +1244,9 @@ _test_match_spec_device(const GSList *specs, const char *match_str)
 {
     if (match_str && g_str_has_prefix(match_str, MATCH_S390))
         return nm_match_spec_device(specs,
-                                    NULL,
-                                    NULL,
-                                    NULL,
-                                    NULL,
-                                    NULL,
-                                    &match_str[NM_STRLEN(MATCH_S390)],
-                                    NULL);
+                                    &((const NMMatchSpecDeviceData){
+                                        .s390_subchannels = &match_str[NM_STRLEN(MATCH_S390)],
+                                    }));
     if (match_str && g_str_has_prefix(match_str, MATCH_DRIVER)) {
         gs_free char *s = g_strdup(&match_str[NM_STRLEN(MATCH_DRIVER)]);
         char         *t;
@@ -1259,9 +1256,16 @@ _test_match_spec_device(const GSList *specs, const char *match_str)
             t[0] = '\0';
             t++;
         }
-        return nm_match_spec_device(specs, NULL, NULL, s, t, NULL, NULL, NULL);
+        return nm_match_spec_device(specs,
+                                    &((const NMMatchSpecDeviceData){
+                                        .driver         = s,
+                                        .driver_version = t,
+                                    }));
     }
-    return nm_match_spec_device(specs, match_str, NULL, NULL, NULL, NULL, NULL, NULL);
+    return nm_match_spec_device(specs,
+                                &((const NMMatchSpecDeviceData){
+                                    .interface_name = match_str,
+                                }));
 }
 
 static void
@@ -2109,7 +2113,7 @@ do_test_stable_id_parse(const char       *stable_id,
         g_assert(!expected_generated);
 
     if (expected_stable_type == NM_UTILS_STABLE_TYPE_UUID)
-        g_assert(!stable_id);
+        g_assert(NM_IN_STRSET(stable_id, NULL, "default${CONNECTION}"));
     else
         g_assert(stable_id);
 
@@ -2137,6 +2141,7 @@ test_stable_id_parse(void)
 #define _parse_random(stable_id) \
     do_test_stable_id_parse("" stable_id "", NM_UTILS_STABLE_TYPE_RANDOM, NULL)
     do_test_stable_id_parse(NULL, NM_UTILS_STABLE_TYPE_UUID, NULL);
+    do_test_stable_id_parse("default${CONNECTION}", NM_UTILS_STABLE_TYPE_UUID, NULL);
     _parse_stable_id("");
     _parse_stable_id("a");
     _parse_stable_id("a$");
@@ -2151,6 +2156,7 @@ test_stable_id_parse(void)
     _parse_stable_id("a$${CONNECTION}");
     _parse_stable_id("a$${CONNECTION}x");
     _parse_generated("${CONNECTION}", "${CONNECTION}=11{_CONNECTION}");
+    _parse_generated(" ${CONNECTION}", " ${CONNECTION}=11{_CONNECTION}");
     _parse_generated("${${CONNECTION}", "${${CONNECTION}=11{_CONNECTION}");
     _parse_generated("${CONNECTION}x", "${CONNECTION}=11{_CONNECTION}x");
     _parse_generated("x${CONNECTION}", "x${CONNECTION}=11{_CONNECTION}");
@@ -2580,6 +2586,125 @@ test_connectivity_state_cmp(void)
 
 /*****************************************************************************/
 
+static void
+test_nm_firewall_nft_stdio_mlag(void)
+{
+#define _T(up,                                               \
+           bond_ifname,                                      \
+           bond_ifnames_down,                                \
+           active_members,                                   \
+           previous_members,                                 \
+           with_counters,                                    \
+           expected)                                         \
+    G_STMT_START                                             \
+    {                                                        \
+        gs_unref_bytes GBytes *_b = NULL;                    \
+                                                             \
+        _b = nm_firewall_nft_stdio_mlag((up),                \
+                                        (bond_ifname),       \
+                                        (bond_ifnames_down), \
+                                        (active_members),    \
+                                        (previous_members),  \
+                                        (with_counters));    \
+                                                             \
+        g_assert(_b);                                        \
+        nmtst_assert_cmpmem(expected,                        \
+                            NM_STRLEN(expected),             \
+                            g_bytes_get_data(_b, NULL),      \
+                            g_bytes_get_size(_b));           \
+    }                                                        \
+    G_STMT_END
+
+    _T(TRUE,
+       "bond0",
+       NM_MAKE_STRV("eth0"),
+       NM_MAKE_STRV("eth1"),
+       NM_MAKE_STRV("eth2"),
+       TRUE,
+       "add table netdev nm-mlag-eth0\012delete table netdev nm-mlag-eth0\012add table netdev "
+       "nm-mlag-bond0\012flush table netdev nm-mlag-bond0\012add chain netdev nm-mlag-bond0 "
+       "rx-drop-bc-mc-eth2 { type filter hook ingress device eth2 priority filter; }\012delete "
+       "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth2\012add chain netdev nm-mlag-bond0 "
+       "rx-drop-bc-mc-eth1 { type filter hook ingress device eth1 priority filter; }\012delete "
+       "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth1\012add set netdev nm-mlag-bond0 "
+       "macset-tagged { typeof ether saddr . vlan id; flags dynamic,timeout; }\012add set netdev "
+       "nm-mlag-bond0 macset-untagged { typeof ether saddr; flags dynamic,timeout; }\012add chain "
+       "netdev nm-mlag-bond0 tx-snoop-source-mac { type filter hook egress device bond0 priority "
+       "filter; }\012add rule netdev nm-mlag-bond0 tx-snoop-source-mac set update ether saddr . "
+       "vlan id timeout 5s @macset-tagged counter return\012add rule netdev nm-mlag-bond0 "
+       "tx-snoop-source-mac set update ether saddr timeout 5s @macset-untagged counter\012add "
+       "chain netdev nm-mlag-bond0 rx-drop-looped-packets { type filter hook ingress device bond0 "
+       "priority filter; }\012add rule netdev nm-mlag-bond0 rx-drop-looped-packets ether saddr . "
+       "vlan id @macset-tagged counter drop\012add rule netdev nm-mlag-bond0 "
+       "rx-drop-looped-packets ether type vlan counter return\012add rule netdev nm-mlag-bond0 "
+       "rx-drop-looped-packets ether saddr @macset-untagged counter drop\012");
+
+    _T(TRUE,
+       "bond0",
+       NM_MAKE_STRV("eth0"),
+       NM_MAKE_STRV("eth1"),
+       NM_MAKE_STRV("eth2"),
+       FALSE,
+       "add table netdev nm-mlag-eth0\012delete table netdev nm-mlag-eth0\012add table netdev "
+       "nm-mlag-bond0\012flush table netdev nm-mlag-bond0\012add chain netdev nm-mlag-bond0 "
+       "rx-drop-bc-mc-eth2 { type filter hook ingress device eth2 priority filter; }\012delete "
+       "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth2\012add chain netdev nm-mlag-bond0 "
+       "rx-drop-bc-mc-eth1 { type filter hook ingress device eth1 priority filter; }\012delete "
+       "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth1\012add set netdev nm-mlag-bond0 "
+       "macset-tagged { typeof ether saddr . vlan id; flags dynamic,timeout; }\012add set netdev "
+       "nm-mlag-bond0 macset-untagged { typeof ether saddr; flags dynamic,timeout; }\012add chain "
+       "netdev nm-mlag-bond0 tx-snoop-source-mac { type filter hook egress device bond0 priority "
+       "filter; }\012add rule netdev nm-mlag-bond0 tx-snoop-source-mac set update ether saddr . "
+       "vlan id timeout 5s @macset-tagged return\012add rule netdev nm-mlag-bond0 "
+       "tx-snoop-source-mac set update ether saddr timeout 5s @macset-untagged\012add chain netdev "
+       "nm-mlag-bond0 rx-drop-looped-packets { type filter hook ingress device bond0 priority "
+       "filter; }\012add rule netdev nm-mlag-bond0 rx-drop-looped-packets ether saddr . vlan id "
+       "@macset-tagged drop\012add rule netdev nm-mlag-bond0 rx-drop-looped-packets ether type "
+       "vlan return\012add rule netdev nm-mlag-bond0 rx-drop-looped-packets ether saddr "
+       "@macset-untagged drop\012");
+
+    _T(TRUE,
+       "bond0",
+       NM_MAKE_STRV("eth0", "eth1"),
+       NM_MAKE_STRV("eth2", "eth3"),
+       NM_MAKE_STRV("eth4", "eth5"),
+       FALSE,
+       "add table netdev nm-mlag-eth0\012delete table netdev nm-mlag-eth0\012add table netdev "
+       "nm-mlag-eth1\012delete table netdev nm-mlag-eth1\012add table netdev "
+       "nm-mlag-bond0\012flush table netdev nm-mlag-bond0\012add chain netdev nm-mlag-bond0 "
+       "rx-drop-bc-mc-eth4 { type filter hook ingress device eth4 priority filter; }\012delete "
+       "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth4\012add chain netdev nm-mlag-bond0 "
+       "rx-drop-bc-mc-eth5 { type filter hook ingress device eth5 priority filter; }\012delete "
+       "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth5\012add chain netdev nm-mlag-bond0 "
+       "rx-drop-bc-mc-eth2 { type filter hook ingress device eth2 priority filter; }\012delete "
+       "chain netdev nm-mlag-bond0 rx-drop-bc-mc-eth2\012add chain netdev nm-mlag-bond0 "
+       "rx-drop-bc-mc-eth3 { type filter hook ingress device eth3 priority filter; }\012add rule "
+       "netdev nm-mlag-bond0 rx-drop-bc-mc-eth3 pkttype { broadcast, multicast } drop\012add set "
+       "netdev nm-mlag-bond0 macset-tagged { typeof ether saddr . vlan id; flags dynamic,timeout; "
+       "}\012add set netdev nm-mlag-bond0 macset-untagged { typeof ether saddr; flags "
+       "dynamic,timeout; }\012add chain netdev nm-mlag-bond0 tx-snoop-source-mac { type filter "
+       "hook egress device bond0 priority filter; }\012add rule netdev nm-mlag-bond0 "
+       "tx-snoop-source-mac set update ether saddr . vlan id timeout 5s @macset-tagged "
+       "return\012add rule netdev nm-mlag-bond0 tx-snoop-source-mac set update ether saddr timeout "
+       "5s @macset-untagged\012add chain netdev nm-mlag-bond0 rx-drop-looped-packets { type filter "
+       "hook ingress device bond0 priority filter; }\012add rule netdev nm-mlag-bond0 "
+       "rx-drop-looped-packets ether saddr . vlan id @macset-tagged drop\012add rule netdev "
+       "nm-mlag-bond0 rx-drop-looped-packets ether type vlan return\012add rule netdev "
+       "nm-mlag-bond0 rx-drop-looped-packets ether saddr @macset-untagged drop\012");
+
+    _T(FALSE,
+       "bond0",
+       NM_MAKE_STRV("eth0", "eth1"),
+       NM_MAKE_STRV("eth2", "eth3"),
+       NM_MAKE_STRV("eth4", "eth5"),
+       FALSE,
+       "add table netdev nm-mlag-eth0\012delete table netdev nm-mlag-eth0\012add table netdev "
+       "nm-mlag-eth1\012delete table netdev nm-mlag-eth1\012add table netdev "
+       "nm-mlag-bond0\012delete table netdev nm-mlag-bond0\012");
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -2654,5 +2779,7 @@ main(int argc, char **argv)
     g_test_add_func("/core/general/test_kernel_cmdline_match_check",
                     test_kernel_cmdline_match_check);
 
+    g_test_add_func("/core/test_nm_firewall_nft_stdio_mlag", test_nm_firewall_nft_stdio_mlag);
+
     return g_test_run();
 }
diff --git a/src/libnm-base/nm-base.c b/src/libnm-base/nm-base.c
index fa64372f..6e164908 100644
--- a/src/libnm-base/nm-base.c
+++ b/src/libnm-base/nm-base.c
@@ -39,3 +39,37 @@ nm_dhcp_iaid_from_hexstr(const char *str, guint32 *out_value)
     NM_SET_OUT(out_value, be32toh(iaid.num));
     return TRUE;
 }
+
+/*****************************************************************************/
+
+/* nm_net_devname_infiniband:
+ * @name: the output-buffer where the value will be written. Must be
+ *   not %NULL and point to a string buffer of at least IFNAMSIZ bytes.
+ * @parent_name: the parent interface name
+ * @p_key: the partition key.
+ *
+ * Returns: the infiniband name will be written to @name and @name
+ *   is returned.
+ */
+const char *
+nm_net_devname_infiniband(char name[static NM_IFNAMSIZ], const char *parent_name, int p_key)
+{
+    g_return_val_if_fail(name, NULL);
+    g_return_val_if_fail(parent_name && parent_name[0], NULL);
+    g_return_val_if_fail(strlen(parent_name) < NM_IFNAMSIZ, NULL);
+
+    /* technically, p_key of 0x0000 and 0x8000 is not allowed either. But we don't
+     * want to assert against that in nm_net_devname_infiniband(). So be more
+     * resilient here, and accept those. */
+    g_return_val_if_fail(p_key >= 0 && p_key <= 0xffff, NULL);
+
+    nm_assert(nm_utils_ifname_valid_kernel(parent_name, NULL));
+
+    /* If parent+suffix is too long, kernel would just truncate
+     * the name. We do the same. See ipoib_vlan_add().  */
+    g_snprintf(name, NM_IFNAMSIZ, "%s.%04x", parent_name, p_key);
+
+    nm_assert(nm_utils_ifname_valid_kernel(name, NULL));
+
+    return name;
+}
diff --git a/src/libnm-base/nm-base.h b/src/libnm-base/nm-base.h
index 440fbc90..65fc9299 100644
--- a/src/libnm-base/nm-base.h
+++ b/src/libnm-base/nm-base.h
@@ -432,4 +432,9 @@ char *nm_dhcp_iaid_to_hexstr(guint32 iaid, char buf[static NM_DHCP_IAID_TO_HEXST
 
 gboolean nm_dhcp_iaid_from_hexstr(const char *str, guint32 *out_value);
 
+/*****************************************************************************/
+
+const char *
+nm_net_devname_infiniband(char name[static NM_IFNAMSIZ], const char *parent_name, int p_key);
+
 #endif /* __NM_LIBNM_BASE_H__ */
diff --git a/src/libnm-base/nm-config-base.h b/src/libnm-base/nm-config-base.h
index c413e867..c90efc1b 100644
--- a/src/libnm-base/nm-config-base.h
+++ b/src/libnm-base/nm-config-base.h
@@ -30,6 +30,7 @@
 #define NM_CONFIG_KEYFILE_KEY_MAIN_HOSTNAME_MODE               "hostname-mode"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_IGNORE_CARRIER              "ignore-carrier"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_IWD_CONFIG_PATH             "iwd-config-path"
+#define NM_CONFIG_KEYFILE_KEY_MAIN_MIGRATE_IFCFG_RH            "migrate-ifcfg-rh"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_MONITOR_CONNECTION_FILES    "monitor-connection-files"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_NO_AUTO_DEFAULT             "no-auto-default"
 #define NM_CONFIG_KEYFILE_KEY_MAIN_PLUGINS                     "plugins"
@@ -50,6 +51,7 @@
 #define NM_CONFIG_KEYFILE_KEY_KEYFILE_PATH              "path"
 #define NM_CONFIG_KEYFILE_KEY_KEYFILE_UNMANAGED_DEVICES "unmanaged-devices"
 #define NM_CONFIG_KEYFILE_KEY_KEYFILE_HOSTNAME          "hostname"
+#define NM_CONFIG_KEYFILE_KEY_KEYFILE_RENAME            "rename"
 
 #define NM_CONFIG_KEYFILE_KEY_IFUPDOWN_MANAGED "managed"
 
diff --git a/src/libnm-client-impl/libnm.ver b/src/libnm-client-impl/libnm.ver
index 3fbd701d..919ffc74 100644
--- a/src/libnm-client-impl/libnm.ver
+++ b/src/libnm-client-impl/libnm.ver
@@ -1901,7 +1901,6 @@ global:
 	nm_range_unref;
 	nm_setting_ip_config_get_auto_route_ext_gw;
 	nm_setting_ip_config_get_dhcp_iaid;
-	nm_setting_ip_config_get_dhcp_iaid;
 	nm_setting_ip_tunnel_get_fwmark;
 	nm_setting_loopback_get_mtu;
 	nm_setting_loopback_get_type;
@@ -1930,3 +1929,17 @@ global:
 	nm_active_connection_get_controller;
 	nm_setting_ip_config_get_replace_local_rule;
 } libnm_1_42_0;
+
+libnm_1_44_0 {
+global:
+	nm_remote_connection_get_version_id;
+	nm_setting_gsm_get_initial_eps_apn;
+	nm_setting_gsm_get_initial_eps_config;
+	nm_setting_ip6_config_get_dhcp_pd_hint;
+	nm_setting_link_get_gro_max_size;
+	nm_setting_link_get_gso_max_segments;
+	nm_setting_link_get_gso_max_size;
+	nm_setting_link_get_tx_queue_length;
+	nm_setting_link_get_type;
+	nm_setting_link_new;
+} libnm_1_42_0;
diff --git a/src/libnm-client-impl/meson.build b/src/libnm-client-impl/meson.build
index 143126c5..fb879dca 100644
--- a/src/libnm-client-impl/meson.build
+++ b/src/libnm-client-impl/meson.build
@@ -142,6 +142,7 @@ if enable_introspection
       src_inc,
       top_inc,
     ],
+    header: 'NetworkManager.h',
     dependencies: [
       declare_dependency(
         compile_args: [
diff --git a/src/libnm-client-impl/nm-active-connection.c b/src/libnm-client-impl/nm-active-connection.c
index ca6b18f9..6a138f3e 100644
--- a/src/libnm-client-impl/nm-active-connection.c
+++ b/src/libnm-client-impl/nm-active-connection.c
@@ -383,9 +383,9 @@ nm_active_connection_get_vpn(NMActiveConnection *connection)
  *
  * Gets the master #NMDevice of the connection.
  *
- * This is replaced by nm_active_connection_get_controller() since 1.44 and 1.42.2.
- *
  * Returns: (transfer none): the master #NMDevice of the #NMActiveConnection.
+ *
+ * Deprecated: 1.44: Use nm_active_connection_get_controller() instead.
  **/
 NMDevice *
 nm_active_connection_get_master(NMActiveConnection *connection)
@@ -402,7 +402,7 @@ nm_active_connection_get_master(NMActiveConnection *connection)
  *
  * Returns: (transfer none): the controller #NMDevice of the #NMActiveConnection.
  *
- * Since: 1.44, 1.42.2
+ * Since: 1.44
  **/
 NMDevice *
 nm_active_connection_get_controller(NMActiveConnection *connection)
@@ -880,8 +880,9 @@ nm_active_connection_class_init(NMActiveConnectionClass *klass)
     /**
      * NMActiveConnection:master:
      *
-     * The master device if one exists. Replaced by the "controller" property
-     * since 1.44 and 1.42.2.
+     * The master device if one exists. Replaced by the "controller" property.
+     *
+     * Deprecated: 1.44
      **/
     obj_properties[PROP_MASTER] = g_param_spec_object(NM_ACTIVE_CONNECTION_MASTER,
                                                       "",
@@ -895,7 +896,7 @@ nm_active_connection_class_init(NMActiveConnectionClass *klass)
      * The controller device if one exists. This replaces the deprecated
      * "master" property.
      *
-     * Since: 1.44, 1.42.2
+     * Since: 1.44
      **/
     obj_properties[PROP_CONTROLLER] =
         g_param_spec_object(NM_ACTIVE_CONNECTION_CONTROLLER,
diff --git a/src/libnm-client-impl/nm-client.c b/src/libnm-client-impl/nm-client.c
index 3ad4b9d6..f3ab6d69 100644
--- a/src/libnm-client-impl/nm-client.c
+++ b/src/libnm-client-impl/nm-client.c
@@ -378,7 +378,7 @@ static void name_owner_changed_cb(GDBusConnection *connection,
 
 static void name_owner_get_call(NMClient *self);
 
-static void _set_nm_running(NMClient *self);
+static void _set_nm_running(NMClient *self, gboolean queue_notify);
 
 /*****************************************************************************/
 
@@ -2724,6 +2724,7 @@ _obj_handle_dbus_changes(NMClient *self, NMLDBusObject *dbobj)
             if (dbobj->dbus_path == _dbus_path_nm) {
                 nm_assert(!priv->dbobj_nm);
                 priv->dbobj_nm = dbobj;
+                _set_nm_running(self, TRUE);
             } else if (dbobj->dbus_path == _dbus_path_settings) {
                 nm_assert(!priv->dbobj_settings);
                 priv->dbobj_settings = dbobj;
@@ -2783,6 +2784,7 @@ _obj_handle_dbus_changes(NMClient *self, NMLDBusObject *dbobj)
             if (dbobj->dbus_path == _dbus_path_nm) {
                 nm_assert(priv->dbobj_nm == dbobj);
                 priv->dbobj_nm = NULL;
+                _set_nm_running(self, TRUE);
                 nml_dbus_property_o_clear_many(priv->nm.property_o,
                                                G_N_ELEMENTS(priv->nm.property_o),
                                                self);
@@ -2936,7 +2938,7 @@ _dbus_handle_changes_commit(NMClient *self, gboolean allow_init_start_check_comp
 
     _nm_client_notify_event_emit(self);
 
-    _set_nm_running(self);
+    _set_nm_running(self, FALSE);
 
     if (allow_init_start_check_complete)
         _init_start_check_complete(self);
@@ -3079,11 +3081,10 @@ _dbus_handle_interface_added(NMClient   *self,
                              const char *object_path,
                              GVariant   *ifaces)
 {
-    gboolean       changed = FALSE;
-    const char    *interface_name;
-    GVariant      *changed_properties;
-    GVariantIter   iter_ifaces;
-    NMLDBusObject *dbobj = NULL;
+    gboolean     changed = FALSE;
+    const char  *interface_name;
+    GVariant    *changed_properties;
+    GVariantIter iter_ifaces;
 
     nm_assert(g_variant_is_of_type(ifaces, G_VARIANT_TYPE("a{sa{sv}}")));
 
@@ -3097,7 +3098,7 @@ _dbus_handle_interface_added(NMClient   *self,
                                             interface_name,
                                             TRUE,
                                             changed_properties,
-                                            &dbobj))
+                                            NULL))
             changed = TRUE;
     }
 
@@ -4084,15 +4085,18 @@ nm_client_get_startup(NMClient *client)
 }
 
 static void
-_set_nm_running(NMClient *self)
+_set_nm_running(NMClient *self, gboolean queue_notify)
 {
     NMClientPrivate *priv = NM_CLIENT_GET_PRIVATE(self);
     gboolean         nm_running;
 
-    nm_running = priv->name_owner && !priv->get_managed_objects_cancellable;
+    nm_running = priv->dbobj_nm && priv->name_owner && !priv->get_managed_objects_cancellable;
     if (priv->nm_running != nm_running) {
         priv->nm_running = nm_running;
-        _notify(self, PROP_NM_RUNNING);
+        if (queue_notify) {
+            _nm_client_queue_notify_object(self, self, obj_properties[PROP_NM_RUNNING]);
+        } else
+            _notify(self, PROP_NM_RUNNING);
     }
 }
 
@@ -4167,7 +4171,7 @@ nm_client_networking_get_enabled(NMClient *client)
  * nm_client_networking_set_enabled:
  * @client: a #NMClient
  * @enabled: %TRUE to set networking enabled, %FALSE to set networking disabled
- * @error: (allow-none): return location for a #GError, or %NULL
+ * @error: return location for a #GError, or %NULL
  *
  * Enables or disables networking.  When networking is disabled, all controlled
  * interfaces are disconnected and deactivated.  When networking is enabled,
@@ -4461,10 +4465,10 @@ nm_client_connectivity_check_get_uri(NMClient *client)
 /**
  * nm_client_get_logging:
  * @client: a #NMClient
- * @level: (allow-none): return location for logging level string
- * @domains: (allow-none): return location for log domains string. The string is
+ * @level: (out) (optional) (nullable): return location for logging level string
+ * @domains: (out) (optional) (nullable): return location for log domains string. The string is
  *   a list of domains separated by ","
- * @error: (allow-none): return location for a #GError, or %NULL
+ * @error: return location for a #GError, or %NULL
  *
  * Gets NetworkManager current logging level and domains.
  *
@@ -4505,10 +4509,10 @@ nm_client_get_logging(NMClient *client, char **level, char **domains, GError **e
 /**
  * nm_client_set_logging:
  * @client: a #NMClient
- * @level: (allow-none): logging level to set (%NULL or an empty string for no change)
- * @domains: (allow-none): logging domains to set. The string should be a list of log
+ * @level: (nullable): logging level to set (%NULL or an empty string for no change)
+ * @domains: (nullable): logging domains to set. The string should be a list of log
  *   domains separated by ",". (%NULL or an empty string for no change)
- * @error: (allow-none): return location for a #GError, or %NULL
+ * @error: return location for a #GError, or %NULL
  *
  * Sets NetworkManager logging level and/or domains.
  *
@@ -4729,7 +4733,7 @@ nm_client_check_connectivity_finish(NMClient *client, GAsyncResult *result, GErr
 /**
  * nm_client_save_hostname:
  * @client: the %NMClient
- * @hostname: (allow-none): the new persistent hostname to set, or %NULL to
+ * @hostname: (nullable): the new persistent hostname to set, or %NULL to
  *   clear any existing persistent hostname
  * @cancellable: a #GCancellable, or %NULL
  * @error: return location for #GError
@@ -4765,7 +4769,7 @@ nm_client_save_hostname(NMClient     *client,
 /**
  * nm_client_save_hostname_async:
  * @client: the %NMClient
- * @hostname: (allow-none): the new persistent hostname to set, or %NULL to
+ * @hostname: (nullable): the new persistent hostname to set, or %NULL to
  *   clear any existing persistent hostname
  * @cancellable: a #GCancellable, or %NULL
  * @callback: (scope async): callback to be called when the operation completes
@@ -5022,9 +5026,9 @@ activate_connection_cb(GObject *object, GAsyncResult *result, gpointer user_data
 /**
  * nm_client_activate_connection_async:
  * @client: a #NMClient
- * @connection: (allow-none): an #NMConnection
- * @device: (allow-none): the #NMDevice
- * @specific_object: (allow-none): the object path of a connection-type-specific
+ * @connection: (nullable): an #NMConnection
+ * @device: (nullable): the #NMDevice
+ * @specific_object: (nullable): the object path of a connection-type-specific
  *   object this activation should use. This parameter is currently ignored for
  *   wired and mobile broadband connections, and the value of %NULL should be used
  *   (ie, no specific object).  For Wi-Fi or WiMAX connections, pass the object
@@ -5258,11 +5262,11 @@ _add_and_activate_connection(NMClient           *self,
 /**
  * nm_client_add_and_activate_connection_async:
  * @client: a #NMClient
- * @partial: (allow-none): an #NMConnection to add; the connection may be
+ * @partial: (nullable): an #NMConnection to add; the connection may be
  *   partially filled (or even %NULL) and will be completed by NetworkManager
  *   using the given @device and @specific_object before being added
- * @device: (allow-none): the #NMDevice
- * @specific_object: (allow-none): the object path of a connection-type-specific
+ * @device: (nullable): the #NMDevice
+ * @specific_object: (nullable): the object path of a connection-type-specific
  *   object this activation should use. This parameter is currently ignored for
  *   wired and mobile broadband connections, and the value of %NULL should be used
  *   (ie, no specific object).  For Wi-Fi or WiMAX connections, pass the object
@@ -5332,11 +5336,11 @@ nm_client_add_and_activate_connection_finish(NMClient *client, GAsyncResult *res
 /**
  * nm_client_add_and_activate_connection2:
  * @client: a #NMClient
- * @partial: (allow-none): an #NMConnection to add; the connection may be
+ * @partial: (nullable): an #NMConnection to add; the connection may be
  *   partially filled (or even %NULL) and will be completed by NetworkManager
  *   using the given @device and @specific_object before being added
- * @device: (allow-none): the #NMDevice
- * @specific_object: (allow-none): the object path of a connection-type-specific
+ * @device: (nullable): the #NMDevice
+ * @specific_object: (nullable): the object path of a connection-type-specific
  *   object this activation should use. This parameter is currently ignored for
  *   wired and mobile broadband connections, and the value of %NULL should be used
  *   (i.e., no specific object).  For Wi-Fi or WiMAX connections, pass the object
@@ -5398,7 +5402,7 @@ nm_client_add_and_activate_connection2(NMClient           *client,
  * @client: an #NMClient
  * @result: the result passed to the #GAsyncReadyCallback
  * @error: location for a #GError, or %NULL
- * @out_result: (allow-none) (transfer full) (out): the output result
+ * @out_result: (out) (optional) (nullable) (transfer full): the output result
  *   of type "a{sv}" returned by D-Bus' AddAndActivate2 call. Currently, no
  *   output is implemented yet.
  *
@@ -5829,7 +5833,7 @@ nm_client_add_connection_finish(NMClient *client, GAsyncResult *result, GError *
  * @client: the %NMClient
  * @settings: the "a{sa{sv}}" #GVariant with the content of the setting.
  * @flags: the %NMSettingsAddConnection2Flags argument.
- * @args: (allow-none): the "a{sv}" #GVariant with extra argument or %NULL
+ * @args: (nullable): the "a{sv}" #GVariant with extra argument or %NULL
  *   for no extra arguments.
  * @ignore_out_result: this function wraps AddConnection2(), which has an
  *   additional result "a{sv}" output parameter. By setting this to %TRUE,
@@ -5871,11 +5875,11 @@ nm_client_add_connection2(NMClient                     *client,
  * nm_client_add_connection2_finish:
  * @client: the #NMClient
  * @result: the #GAsyncResult
- * @out_result: (allow-none) (transfer full) (out): the output #GVariant
- *   from AddConnection2().
+ * @out_result: (out) (optional) (nullable) (transfer full): the output
+ *   #GVariant from AddConnection2().
  *   If you care about the output result, then the "ignore_out_result"
  *   parameter of nm_client_add_connection2() must not be set to %TRUE.
- * @error: (allow-none): the error argument.
+ * @error: the error argument.
  *
  * Returns: (transfer full): on success, a pointer to the added
  *   #NMRemoteConnection.
@@ -6048,7 +6052,7 @@ nm_client_load_connections_finish(NMClient     *client,
  * files from disk, adding, updating, and removing connections until
  * the in-memory state matches the on-disk state.
  *
- * Return value: %TRUE on success, %FALSE on failure
+ * Returns: %TRUE on success, %FALSE on failure
  *
  * Deprecated: 1.22: Use nm_client_reload_connections_async() or GDBusConnection.
  **/
@@ -6121,7 +6125,7 @@ nm_client_reload_connections_async(NMClient           *client,
  *
  * Gets the result of an nm_client_reload_connections_async() call.
  *
- * Return value: %TRUE on success, %FALSE on failure
+ * Returns: %TRUE on success, %FALSE on failure
  **/
 gboolean
 nm_client_reload_connections_finish(NMClient *client, GAsyncResult *result, GError **error)
@@ -6147,7 +6151,7 @@ nm_client_reload_connections_finish(NMClient *client, GAsyncResult *result, GErr
  *
  * Gets the current DNS processing mode.
  *
- * Return value: the DNS processing mode, or %NULL in case the
+ * Returns: the DNS processing mode, or %NULL in case the
  *   value is not available.
  *
  * Since: 1.6
@@ -6166,7 +6170,7 @@ nm_client_get_dns_mode(NMClient *client)
  *
  * Gets the current DNS resolv.conf manager.
  *
- * Return value: the resolv.conf manager or %NULL in case the
+ * Returns: the resolv.conf manager or %NULL in case the
  *   value is not available.
  *
  * Since: 1.6
@@ -6279,7 +6283,7 @@ _notify_update_prop_dns_manager_configuration(NMClient               *self,
 /**
  * nm_client_get_capabilities:
  * @client: the #NMClient instance
- * @length: (out) (allow-none): the number of returned capabilities.
+ * @length: (out) (optional): the number of returned capabilities.
  *
  * Returns: (transfer none) (array length=length): the
  *   list of capabilities reported by the server or %NULL
@@ -7172,7 +7176,7 @@ name_owner_changed(NMClient *self, const char *name_owner)
     if (changed && priv->name_owner)
         _init_fetch_all(self);
 
-    _set_nm_running(self);
+    _set_nm_running(self, FALSE);
 
     if (priv->init_data) {
         nm_auto_pop_gmaincontext GMainContext *main_context = NULL;
@@ -9059,8 +9063,8 @@ _wait_shutdown_cancelled_cb(GCancellable *cancellable, gpointer user_data)
  *   that the client's maincontext gets iterated so that it can complete.
  *   By integrating the maincontext in the current thread default, you
  *   may instead only iterate the latter.
- * @cancellable: (allow-none): the #GCancellable to abort the shutdown.
- * @callback: (nullable): a #GAsyncReadyCallback to call when the request
+ * @cancellable: the #GCancellable to abort the shutdown.
+ * @callback: a #GAsyncReadyCallback to call when the request
  *   is satisfied or %NULL if you don't care about the result of the
  *   method invocation.
  * @user_data: the data to pass to @callback
diff --git a/src/libnm-client-impl/nm-conn-utils.c b/src/libnm-client-impl/nm-conn-utils.c
index 223c18e6..08f8403c 100644
--- a/src/libnm-client-impl/nm-conn-utils.c
+++ b/src/libnm-client-impl/nm-conn-utils.c
@@ -170,7 +170,7 @@ nm_conn_wireguard_import(const char *filename, GError **error)
                                    NM_UTILS_ERROR_UNKNOWN,
                                    _("The name of the WireGuard config must be a valid interface "
                                      "name followed by \".conf\""));
-        return FALSE;
+        return NULL;
     }
 
     if (!nm_utils_file_get_contents(-1,
@@ -250,7 +250,7 @@ nm_conn_wireguard_import(const char *filename, GError **error)
                                    current_peer_start_line_nr,
                                    filename,
                                    error))
-                return FALSE;
+                return NULL;
             line_context = LINE_CONTEXT_INTERFACE;
             continue;
         }
@@ -261,7 +261,7 @@ nm_conn_wireguard_import(const char *filename, GError **error)
                                    current_peer_start_line_nr,
                                    filename,
                                    error))
-                return FALSE;
+                return NULL;
             current_peer_start_line_nr = line_nr;
             current_peer               = nm_wireguard_peer_new();
             line_context               = LINE_CONTEXT_PEER;
@@ -454,7 +454,7 @@ fail_invalid_line:
                            _("unrecognized line at %s:%zu"),
                            filename,
                            line_nr);
-        return FALSE;
+        return NULL;
 fail_invalid_value:
         nm_utils_error_set(error,
                            NM_UTILS_ERROR_INVALID_ARGUMENT,
@@ -462,7 +462,7 @@ fail_invalid_value:
                            matched_key,
                            filename,
                            line_nr);
-        return FALSE;
+        return NULL;
 fail_invalid_secret:
         nm_utils_error_set(error,
                            NM_UTILS_ERROR_INVALID_ARGUMENT,
@@ -470,7 +470,7 @@ fail_invalid_secret:
                            matched_key,
                            filename,
                            line_nr);
-        return FALSE;
+        return NULL;
     }
 
     if (!_wg_complete_peer(&data_peers,
@@ -478,7 +478,7 @@ fail_invalid_secret:
                            current_peer_start_line_nr,
                            filename,
                            error))
-        return FALSE;
+        return NULL;
 
     connection = nm_simple_connection_new();
     s_con      = NM_SETTING_CONNECTION(nm_setting_connection_new());
@@ -592,7 +592,7 @@ fail_invalid_secret:
                            NM_UTILS_ERROR_INVALID_ARGUMENT,
                            _("Failed to create WireGuard connection: %s"),
                            local->message);
-        return FALSE;
+        return NULL;
     }
 
     return g_steal_pointer(&connection);
diff --git a/src/libnm-client-impl/nm-device-bond.c b/src/libnm-client-impl/nm-device-bond.c
index e8b3e3eb..affcb993 100644
--- a/src/libnm-client-impl/nm-device-bond.c
+++ b/src/libnm-client-impl/nm-device-bond.c
@@ -82,7 +82,7 @@ nm_device_bond_get_carrier(NMDeviceBond *device)
  * #NMDevices that are slaves of @device. This is the internal
  * copy used by the device, and must not be modified.
  *
- * Deprecated: 1.34 Use nm_device_get_ports() instead.
+ * Deprecated: 1.34: Use nm_device_get_ports() instead.
  **/
 const GPtrArray *
 nm_device_bond_get_slaves(NMDeviceBond *device)
diff --git a/src/libnm-client-impl/nm-device-bridge.c b/src/libnm-client-impl/nm-device-bridge.c
index 6d5d6f95..abb78ced 100644
--- a/src/libnm-client-impl/nm-device-bridge.c
+++ b/src/libnm-client-impl/nm-device-bridge.c
@@ -82,7 +82,7 @@ nm_device_bridge_get_carrier(NMDeviceBridge *device)
  * #NMDevices that are slaves of @device. This is the internal
  * copy used by the device, and must not be modified.
  *
- * Deprecated: 1.34 Use nm_device_get_ports() instead.
+ * Deprecated: 1.34: Use nm_device_get_ports() instead.
  **/
 const GPtrArray *
 nm_device_bridge_get_slaves(NMDeviceBridge *device)
diff --git a/src/libnm-client-impl/nm-device-ovs-bridge.c b/src/libnm-client-impl/nm-device-ovs-bridge.c
index 8c30fc3f..ef134798 100644
--- a/src/libnm-client-impl/nm-device-ovs-bridge.c
+++ b/src/libnm-client-impl/nm-device-ovs-bridge.c
@@ -44,7 +44,7 @@ G_DEFINE_TYPE(NMDeviceOvsBridge, nm_device_ovs_bridge, NM_TYPE_DEVICE)
  *
  * Since: 1.14
  *
- * Deprecated: 1.34 Use nm_device_get_ports() instead.
+ * Deprecated: 1.34: Use nm_device_get_ports() instead.
  **/
 const GPtrArray *
 nm_device_ovs_bridge_get_slaves(NMDeviceOvsBridge *device)
diff --git a/src/libnm-client-impl/nm-device-ovs-port.c b/src/libnm-client-impl/nm-device-ovs-port.c
index 9e20e5b8..5bcef73c 100644
--- a/src/libnm-client-impl/nm-device-ovs-port.c
+++ b/src/libnm-client-impl/nm-device-ovs-port.c
@@ -44,7 +44,7 @@ G_DEFINE_TYPE(NMDeviceOvsPort, nm_device_ovs_port, NM_TYPE_DEVICE)
  *
  * Since: 1.14
  *
- * Deprecated: 1.34 Use nm_device_get_ports() instead.
+ * Deprecated: 1.34: Use nm_device_get_ports() instead.
  **/
 const GPtrArray *
 nm_device_ovs_port_get_slaves(NMDeviceOvsPort *device)
diff --git a/src/libnm-client-impl/nm-device-team.c b/src/libnm-client-impl/nm-device-team.c
index 0a42089a..e0b21e8e 100644
--- a/src/libnm-client-impl/nm-device-team.c
+++ b/src/libnm-client-impl/nm-device-team.c
@@ -83,7 +83,7 @@ nm_device_team_get_carrier(NMDeviceTeam *device)
  * #NMDevices that are slaves of @device. This is the internal
  * copy used by the device, and must not be modified.
  *
- * Deprecated: 1.34 Use nm_device_get_ports() instead.
+ * Deprecated: 1.34: Use nm_device_get_ports() instead.
  **/
 const GPtrArray *
 nm_device_team_get_slaves(NMDeviceTeam *device)
diff --git a/src/libnm-client-impl/nm-device-wifi-p2p.c b/src/libnm-client-impl/nm-device-wifi-p2p.c
index 2b3b82f8..535482ad 100644
--- a/src/libnm-client-impl/nm-device-wifi-p2p.c
+++ b/src/libnm-client-impl/nm-device-wifi-p2p.c
@@ -130,7 +130,7 @@ nm_device_wifi_p2p_get_peer_by_path(NMDeviceWifiP2P *device, const char *path)
 /**
  * nm_device_wifi_p2p_start_find:
  * @device: a #NMDeviceWifiP2P
- * @options: (allow-none): optional options passed to StartFind.
+ * @options: (nullable): optional options passed to StartFind.
  * @cancellable: a #GCancellable, or %NULL
  * @callback: a #GAsyncReadyCallback, or %NULL
  * @user_data: user_data for @callback
diff --git a/src/libnm-client-impl/nm-device.c b/src/libnm-client-impl/nm-device.c
index d5ecee48..e3e6ca6e 100644
--- a/src/libnm-client-impl/nm-device.c
+++ b/src/libnm-client-impl/nm-device.c
@@ -2366,7 +2366,7 @@ nm_device_is_software(NMDevice *device)
 /**
  * nm_device_reapply:
  * @device: a #NMDevice
- * @connection: (allow-none): the #NMConnection to replace the applied
+ * @connection: (nullable): the #NMConnection to replace the applied
  *   settings with or %NULL to reuse existing
  * @version_id: zero or the expected version id of the applied connection.
  *   If specified and the version id mismatches, the call fails without
@@ -2420,7 +2420,7 @@ nm_device_reapply(NMDevice     *device,
 /**
  * nm_device_reapply_async:
  * @device: a #NMDevice
- * @connection: (allow-none): the #NMConnection to replace the applied
+ * @connection: (nullable): the #NMConnection to replace the applied
  *   settings with or %NULL to reuse existing
  * @version_id: zero or the expected version id of the applied
  *   connection. If specified and the version id mismatches, the call
@@ -2500,7 +2500,7 @@ nm_device_reapply_finish(NMDevice *device, GAsyncResult *result, GError **error)
  * nm_device_get_applied_connection:
  * @device: a #NMDevice
  * @flags: the flags argument. See #NMDeviceReapplyFlags.
- * @version_id: (out) (allow-none): returns the current version id of
+ * @version_id: (out) (optional): returns the current version id of
  *   the applied connection
  * @cancellable: a #GCancellable, or %NULL
  * @error: location for a #GError, or %NULL
@@ -2601,7 +2601,7 @@ nm_device_get_applied_connection_async(NMDevice           *device,
  * nm_device_get_applied_connection_finish:
  * @device: a #NMDevice
  * @result: the result passed to the #GAsyncReadyCallback
- * @version_id: (out) (allow-none): the current version id of the applied
+ * @version_id: (out) (optional): the current version id of the applied
  *   connection.
  * @error: location for a #GError, or %NULL
  *
@@ -3108,7 +3108,8 @@ nm_lldp_neighbor_get_attr_names(NMLldpNeighbor *neighbor)
  * nm_lldp_neighbor_get_attr_string_value:
  * @neighbor: the #NMLldpNeighbor
  * @name: the attribute name
- * @out_value: (out) (allow-none) (transfer none): on return, the attribute value
+ * @out_value: (out) (optional) (nullable) (transfer none): on return, the
+ *   attribute value
  *
  * Gets the string value of attribute with name @name on @neighbor
  *
@@ -3135,7 +3136,7 @@ nm_lldp_neighbor_get_attr_string_value(NMLldpNeighbor *neighbor,
  * nm_lldp_neighbor_get_attr_uint_value:
  * @neighbor: the #NMLldpNeighbor
  * @name: the attribute name
- * @out_value: (out) (allow-none): on return, the attribute value
+ * @out_value: (out) (optional): on return, the attribute value
  *
  * Gets the uint32 value of attribute with name @name on @neighbor
  *
diff --git a/src/libnm-client-impl/nm-remote-connection.c b/src/libnm-client-impl/nm-remote-connection.c
index fe7e0712..607c21a1 100644
--- a/src/libnm-client-impl/nm-remote-connection.c
+++ b/src/libnm-client-impl/nm-remote-connection.c
@@ -31,12 +31,14 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMRemoteConnection,
                              PROP_UNSAVED,
                              PROP_FLAGS,
                              PROP_FILENAME,
+                             PROP_VERSION_ID,
                              PROP_VISIBLE, );
 
 typedef struct {
     GCancellable *get_settings_cancellable;
 
     char   *filename;
+    guint64 version_id;
     guint32 flags;
     bool    unsaved;
 
@@ -69,9 +71,9 @@ G_DEFINE_TYPE_WITH_CODE(NMRemoteConnection,
 /**
  * nm_remote_connection_update2:
  * @connection: the #NMRemoteConnection
- * @settings: (allow-none): optional connection to update the settings.
+ * @settings: (nullable): optional connection to update the settings.
  * @flags: update-flags
- * @args: (allow-none): optional arguments.
+ * @args: (nullable): optional arguments.
  * @cancellable: a #GCancellable, or %NULL
  * @callback: callback to be called when the commit operation completes
  * @user_data: caller-specific data passed to @callback
@@ -602,6 +604,23 @@ nm_remote_connection_get_filename(NMRemoteConnection *connection)
 }
 
 /**
+ * nm_remote_connection_get_version_id:
+ * @connection: the #NMRemoteConnection
+ *
+ * Returns: the version-id of the profile. This ID is incremented
+ *   whenever the profile is modified.
+ *
+ * Since: 1.44
+ */
+guint64
+nm_remote_connection_get_version_id(NMRemoteConnection *connection)
+{
+    g_return_val_if_fail(NM_IS_REMOTE_CONNECTION(connection), 0);
+
+    return NM_REMOTE_CONNECTION_GET_PRIVATE(connection)->version_id;
+}
+
+/**
  * nm_remote_connection_get_visible:
  * @connection: the #NMRemoteConnection
  *
@@ -724,6 +743,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     case PROP_FILENAME:
         g_value_set_string(value, NM_REMOTE_CONNECTION_GET_PRIVATE(object)->filename);
         break;
+    case PROP_VERSION_ID:
+        g_value_set_uint64(value, NM_REMOTE_CONNECTION_GET_PRIVATE(object)->version_id);
+        break;
     case PROP_VISIBLE:
         g_value_set_boolean(value, NM_REMOTE_CONNECTION_GET_PRIVATE(object)->visible);
         break;
@@ -759,10 +781,11 @@ const NMLDBusMetaIface _nml_dbus_meta_iface_nm_settings_connection = NML_DBUS_ME
                                       NMRemoteConnection,
                                       _priv.filename),
         NML_DBUS_META_PROPERTY_INIT_U("Flags", PROP_FLAGS, NMRemoteConnection, _priv.flags),
-        NML_DBUS_META_PROPERTY_INIT_B("Unsaved",
-                                      PROP_UNSAVED,
+        NML_DBUS_META_PROPERTY_INIT_B("Unsaved", PROP_UNSAVED, NMRemoteConnection, _priv.unsaved),
+        NML_DBUS_META_PROPERTY_INIT_T("VersionId",
+                                      PROP_VERSION_ID,
                                       NMRemoteConnection,
-                                      _priv.unsaved), ), );
+                                      _priv.version_id), ), );
 
 static void
 nm_remote_connection_class_init(NMRemoteConnectionClass *klass)
@@ -820,6 +843,23 @@ nm_remote_connection_class_init(NMRemoteConnectionClass *klass)
                                                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
     /**
+     * NMRemoteConnection:version-id:
+     *
+     * The version ID of the profile that is incremented when the profile gets modified.
+     * This can be used to track concurrent modifications of the profile.
+     *
+     * Since: 1.44
+     **/
+    obj_properties[PROP_VERSION_ID] =
+        g_param_spec_uint64(NM_REMOTE_CONNECTION_VERSION_ID,
+                            "",
+                            "",
+                            0,
+                            G_MAXUINT64,
+                            0,
+                            G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    /**
      * NMRemoteConnection:visible:
      *
      * %TRUE if the remote connection is visible to the current user, %FALSE if
diff --git a/src/libnm-client-public/NetworkManager.h b/src/libnm-client-public/NetworkManager.h
index da661db9..61adee29 100644
--- a/src/libnm-client-public/NetworkManager.h
+++ b/src/libnm-client-public/NetworkManager.h
@@ -44,17 +44,17 @@
 #include "nm-setting-ip6-config.h"
 #include "nm-setting-ip-config.h"
 #include "nm-setting-ip-tunnel.h"
+#include "nm-setting-link.h"
 #include "nm-setting-loopback.h"
 #include "nm-setting-macsec.h"
 #include "nm-setting-macvlan.h"
 #include "nm-setting-match.h"
 #include "nm-setting-olpc-mesh.h"
 #include "nm-setting-ovs-bridge.h"
-#include "nm-setting-ovs-interface.h"
 #include "nm-setting-ovs-dpdk.h"
+#include "nm-setting-ovs-interface.h"
 #include "nm-setting-ovs-patch.h"
 #include "nm-setting-ovs-port.h"
-#include "nm-setting-wifi-p2p.h"
 #include "nm-setting-ppp.h"
 #include "nm-setting-pppoe.h"
 #include "nm-setting-proxy.h"
@@ -70,6 +70,7 @@
 #include "nm-setting-vpn.h"
 #include "nm-setting-vrf.h"
 #include "nm-setting-vxlan.h"
+#include "nm-setting-wifi-p2p.h"
 #include "nm-setting-wimax.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireguard.h"
diff --git a/src/libnm-client-public/nm-active-connection.h b/src/libnm-client-public/nm-active-connection.h
index 38dff0f3..b4391d0f 100644
--- a/src/libnm-client-public/nm-active-connection.h
+++ b/src/libnm-client-public/nm-active-connection.h
@@ -66,8 +66,9 @@ NMActiveConnectionStateReason nm_active_connection_get_state_reason(NMActiveConn
 
 struct _NMDevice;
 
+_NM_DEPRECATED_INCLUSIVE_LANGUAGE_1_44
 struct _NMDevice *nm_active_connection_get_master(NMActiveConnection *connection);
-NM_AVAILABLE_IN_1_42_2
+NM_AVAILABLE_IN_1_44
 struct _NMDevice *nm_active_connection_get_controller(NMActiveConnection *connection);
 
 gboolean      nm_active_connection_get_default(NMActiveConnection *connection);
diff --git a/src/libnm-client-public/nm-autoptr.h b/src/libnm-client-public/nm-autoptr.h
index 31ae2cce..fde6cccf 100644
--- a/src/libnm-client-public/nm-autoptr.h
+++ b/src/libnm-client-public/nm-autoptr.h
@@ -56,6 +56,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceTeam, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceTun, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceVeth, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceVlan, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceVrf, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceVxlan, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceWifi, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMDeviceWifiP2P, g_object_unref)
@@ -78,18 +79,23 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingDummy, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingEthtool, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingGeneric, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingGsm, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingHostname, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingIP4Config, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingIP6Config, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingIPConfig, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingIPTunnel, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingInfiniband, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingLink, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingLoopback, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingMacsec, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingMacvlan, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingMatch, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOlpcMesh, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsBridge, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsDpdk, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsExternalIDs, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsInterface, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsOtherConfig, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsPatch, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingOvsPort, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingPpp, g_object_unref)
@@ -105,11 +111,12 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingUser, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingVeth, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingVlan, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingVpn, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingVrf, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingVxlan, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingWifiP2P, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingWimax, g_object_unref)
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingWired, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingWireGuard, g_object_unref)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingWired, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingWireless, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingWirelessSecurity, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(NMSettingWpan, g_object_unref)
diff --git a/src/libnm-client-public/nm-remote-connection.h b/src/libnm-client-public/nm-remote-connection.h
index abfeecb3..1b4ea22c 100644
--- a/src/libnm-client-public/nm-remote-connection.h
+++ b/src/libnm-client-public/nm-remote-connection.h
@@ -32,6 +32,7 @@ G_BEGIN_DECLS
 #define NM_REMOTE_CONNECTION_UNSAVED         "unsaved"
 #define NM_REMOTE_CONNECTION_FLAGS           "flags"
 #define NM_REMOTE_CONNECTION_FILENAME        "filename"
+#define NM_REMOTE_CONNECTION_VERSION_ID      "version-id"
 #define NM_REMOTE_CONNECTION_VISIBLE         "visible"
 
 /**
@@ -120,6 +121,9 @@ const char *nm_remote_connection_get_filename(NMRemoteConnection *connection);
 
 gboolean nm_remote_connection_get_visible(NMRemoteConnection *connection);
 
+NM_AVAILABLE_IN_1_44
+guint64 nm_remote_connection_get_version_id(NMRemoteConnection *connection);
+
 G_END_DECLS
 
 #endif /* __NM_REMOTE_CONNECTION__ */
diff --git a/src/libnm-client-test/nm-test-utils-impl.c b/src/libnm-client-test/nm-test-utils-impl.c
index ad642d25..ba541ad8 100644
--- a/src/libnm-client-test/nm-test-utils-impl.c
+++ b/src/libnm-client-test/nm-test-utils-impl.c
@@ -107,7 +107,8 @@ nmtstc_service_init(void)
     g_spawn_async_with_pipes(NULL,
                              (char **) args,
                              NULL,
-                             G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
+                             G_SPAWN_CLOEXEC_PIPES | G_SPAWN_SEARCH_PATH
+                                 | G_SPAWN_DO_NOT_REAP_CHILD,
                              NULL,
                              NULL,
                              &info->pid,
diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.c b/src/libnm-core-aux-intern/nm-libnm-core-utils.c
index c4bfd2bd..3e317e5b 100644
--- a/src/libnm-core-aux-intern/nm-libnm-core-utils.c
+++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.c
@@ -13,7 +13,7 @@
 /*****************************************************************************/
 
 const char **
-nm_utils_bond_option_arp_ip_targets_split(const char *arp_ip_target)
+nm_utils_bond_option_ip_split(const char *arp_ip_target)
 {
     return nm_strsplit_set_full(arp_ip_target, ",", NM_STRSPLIT_SET_FLAGS_STRSTRIP);
 }
@@ -36,6 +36,7 @@ _nm_setting_bond_remove_options_arp_interval(NMSettingBond *s_bond)
 
     nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_ARP_INTERVAL);
     nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+    nm_setting_bond_remove_option(s_bond, NM_SETTING_BOND_OPTION_NS_IP6_TARGET);
 }
 
 /*****************************************************************************/
@@ -87,6 +88,20 @@ NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     {"none", NM_BOND_FAIL_OVER_MAC_NONE}, );
 
 NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
+    _nm_setting_bond_lacp_active_from_string,
+    NMBondLacpActive,
+    {
+        G_STATIC_ASSERT_EXPR(_NM_BOND_LACP_ACTIVE_NUM <= 2);
+
+        if (name && name[0] < '0' + _NM_BOND_LACP_ACTIVE_NUM && name[0] >= '0' && name[1] == '\0') {
+            return name[0] - '0';
+        }
+    },
+    { return NM_BOND_LACP_ACTIVE_ON; },
+    {"off", NM_BOND_LACP_ACTIVE_OFF},
+    {"on", NM_BOND_LACP_ACTIVE_ON}, );
+
+NM_UTILS_STRING_TABLE_LOOKUP_DEFINE(
     _nm_setting_bond_lacp_rate_from_string,
     NMBondLacpRate,
     {
@@ -635,7 +650,7 @@ nm_utils_dnsname_normalize(int addr_family, const char *dns, char **out_free)
 
 /**
  * nm_setting_ovs_other_config_check_key:
- * @key: (allow-none): the key to check
+ * @key: (nullable): the key to check
  * @error: a #GError, %NULL to ignore.
  *
  * Checks whether @key is a valid key for OVS' other-config.
@@ -689,7 +704,7 @@ nm_setting_ovs_other_config_check_key(const char *key, GError **error)
 
 /**
  * nm_setting_ovs_other_config_check_val:
- * @val: (allow-none): the value to check
+ * @val: (nullable): the value to check
  * @error: a #GError, %NULL to ignore.
  *
  * Checks whether @val is a valid user data value. This means,
diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.h b/src/libnm-core-aux-intern/nm-libnm-core-utils.h
index 18af1e1a..b1336731 100644
--- a/src/libnm-core-aux-intern/nm-libnm-core-utils.h
+++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.h
@@ -21,6 +21,13 @@
 #define nm_auto_unref_ip_address nm_auto(_nm_ip_address_unref)
 NM_AUTO_DEFINE_FCN0(NMIPAddress *, _nm_ip_address_unref, nm_ip_address_unref);
 
+static inline NMIPRoute *
+_nm_ip_route_ref(NMIPRoute *route)
+{
+    nm_ip_route_ref(route);
+    return route;
+}
+
 #define nm_auto_unref_ip_route nm_auto(_nm_auto_unref_ip_route)
 NM_AUTO_DEFINE_FCN0(NMIPRoute *, _nm_auto_unref_ip_route, nm_ip_route_unref);
 
@@ -52,7 +59,7 @@ NM_AUTO_DEFINE_FCN0(NMWireGuardPeer *, _nm_auto_unref_wgpeer, nm_wireguard_peer_
 
 /****************************************************************************/
 
-const char **nm_utils_bond_option_arp_ip_targets_split(const char *arp_ip_target);
+const char **nm_utils_bond_option_ip_split(const char *arp_ip_target);
 
 void _nm_setting_bond_remove_options_miimon(NMSettingBond *s_bond);
 void _nm_setting_bond_remove_options_arp_interval(NMSettingBond *s_bond);
@@ -96,6 +103,18 @@ typedef enum {
 NMBondFailOverMac _nm_setting_bond_fail_over_mac_from_string(const char *str);
 
 typedef enum {
+    NM_BOND_LACP_ACTIVE_UNKNOWN = -1,
+
+    /* The numeric values correspond to kernel's numbering. */
+    NM_BOND_LACP_ACTIVE_OFF = 0,
+    NM_BOND_LACP_ACTIVE_ON  = 1,
+
+    _NM_BOND_LACP_ACTIVE_NUM,
+} NMBondLacpActive;
+
+NMBondLacpActive _nm_setting_bond_lacp_active_from_string(const char *str);
+
+typedef enum {
     NM_BOND_LACP_RATE_UNKNOWN = -1,
 
     /* The numeric values correspond to kernel's numbering. */
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 4a6e70fe..4e17a45b 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
@@ -124,6 +124,7 @@
                   gprop-type="guint"
                   />
         <property name="rate"
+                  is-deprecated="1"
                   dbus-type="u"
                   gprop-type="guint"
                   />
@@ -140,6 +141,7 @@
                   gprop-type="GBytes"
                   />
         <property name="tx-power"
+                  is-deprecated="1"
                   dbus-type="u"
                   gprop-type="guint"
                   />
@@ -830,6 +832,7 @@
                   gprop-type="GStrv"
                   />
         <property name="read-only"
+                  is-deprecated="1"
                   dbus-type="b"
                   gprop-type="gboolean"
                   />
@@ -1317,6 +1320,14 @@
                   dbus-type="b"
                   gprop-type="gboolean"
                   />
+        <property name="initial-eps-bearer-apn"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
+        <property name="initial-eps-bearer-configure"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
         <property name="mtu"
                   dbus-type="u"
                   gprop-type="guint"
@@ -1645,6 +1656,10 @@
                   dbus-type="s"
                   gprop-type="gchararray"
                   />
+        <property name="dhcp-pd-hint"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
         <property name="dhcp-reject-servers"
                   dbus-type="as"
                   gprop-type="GStrv"
@@ -1745,6 +1760,26 @@
                   gprop-type="gchararray"
                   />
     </setting>
+    <setting name="link"
+             gtype="NMSettingLink"
+             >
+        <property name="gro-max-size"
+                  dbus-type="x"
+                  gprop-type="gint64"
+                  />
+        <property name="gso-max-segments"
+                  dbus-type="x"
+                  gprop-type="gint64"
+                  />
+        <property name="gso-max-size"
+                  dbus-type="x"
+                  gprop-type="gint64"
+                  />
+        <property name="tx-queue-length"
+                  dbus-type="x"
+                  gprop-type="gint64"
+                  />
+    </setting>
     <setting name="loopback"
              gtype="NMSettingLoopback"
              >
diff --git a/src/libnm-core-impl/meson.build b/src/libnm-core-impl/meson.build
index 6408ae17..635b417b 100644
--- a/src/libnm-core-impl/meson.build
+++ b/src/libnm-core-impl/meson.build
@@ -24,6 +24,7 @@ libnm_core_settings_sources = files(
   'nm-setting-ip-tunnel.c',
   'nm-setting-ip4-config.c',
   'nm-setting-ip6-config.c',
+  'nm-setting-link.c',
   'nm-setting-loopback.c',
   'nm-setting-macsec.c',
   'nm-setting-macvlan.c',
diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c
index b017a6e5..6ffc750a 100644
--- a/src/libnm-core-impl/nm-connection.c
+++ b/src/libnm-core-impl/nm-connection.c
@@ -276,26 +276,7 @@ nm_connection_remove_setting(NMConnection *connection, GType setting_type)
 }
 
 static gpointer
-_connection_get_setting(NMConnection *connection, GType setting_type)
-{
-    NMSetting               *setting;
-    const NMMetaSettingInfo *setting_info;
-
-    nm_assert(NM_IS_CONNECTION(connection));
-    nm_assert(g_type_is_a(setting_type, NM_TYPE_SETTING));
-
-    setting_info = _nm_meta_setting_info_from_gtype(setting_type);
-    if (!setting_info)
-        g_return_val_if_reached(NULL);
-
-    setting = NM_CONNECTION_GET_PRIVATE(connection)->settings[setting_info->meta_type];
-
-    nm_assert(!setting || G_TYPE_CHECK_INSTANCE_TYPE(setting, setting_type));
-    return setting;
-}
-
-static gpointer
-_connection_get_setting_by_meta_type(NMConnectionPrivate *priv, NMMetaSettingType meta_type)
+_get_setting_by_metatype(NMConnectionPrivate *priv, NMMetaSettingType meta_type)
 {
     nm_assert(priv);
     nm_assert(_NM_INT_NOT_NEGATIVE(meta_type));
@@ -304,20 +285,15 @@ _connection_get_setting_by_meta_type(NMConnectionPrivate *priv, NMMetaSettingTyp
     return priv->settings[meta_type];
 }
 
-static gpointer
-_connection_get_setting_check(NMConnection *connection, GType setting_type)
+/* The "unsafe" part here is that _nm_connection_get_setting_by_metatype() has a compile
+ * time check that meta_type is valid. With the unsafe variant, the caller must ensure that,
+ * and we only get an nm_assert() check -- which is basically nothing. */
+gpointer
+_nm_connection_get_setting_by_metatype_unsafe(NMConnection *connection, NMMetaSettingType meta_type)
 {
     g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
 
-    return _connection_get_setting(connection, setting_type);
-}
-
-static gpointer
-_connection_get_setting_by_meta_type_check(NMConnection *connection, NMMetaSettingType meta_type)
-{
-    g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
-
-    return _connection_get_setting_by_meta_type(NM_CONNECTION_GET_PRIVATE(connection), meta_type);
+    return _get_setting_by_metatype(NM_CONNECTION_GET_PRIVATE(connection), meta_type);
 }
 
 /**
@@ -334,19 +310,34 @@ _connection_get_setting_by_meta_type_check(NMConnection *connection, NMMetaSetti
 NMSetting *
 nm_connection_get_setting(NMConnection *connection, GType setting_type)
 {
-    g_return_val_if_fail(g_type_is_a(setting_type, NM_TYPE_SETTING), NULL);
+    NMSetting               *setting;
+    const NMMetaSettingInfo *setting_info;
 
-    return _connection_get_setting_check(connection, setting_type);
+    g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
+
+    setting_info = _nm_meta_setting_info_from_gtype(setting_type);
+
+    if (!setting_info)
+        g_return_val_if_reached(NULL);
+
+    setting = NM_CONNECTION_GET_PRIVATE(connection)->settings[setting_info->meta_type];
+
+    nm_assert(!setting || G_TYPE_CHECK_INSTANCE_TYPE(setting, setting_type));
+
+    return setting;
 }
 
 NMSettingIPConfig *
 nm_connection_get_setting_ip_config(NMConnection *connection, int addr_family)
 {
+    g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
+
     nm_assert_addr_family(addr_family);
 
-    return NM_SETTING_IP_CONFIG(_connection_get_setting(
-        connection,
-        (addr_family == AF_INET) ? NM_TYPE_SETTING_IP4_CONFIG : NM_TYPE_SETTING_IP6_CONFIG));
+    return NM_SETTING_IP_CONFIG(_get_setting_by_metatype(NM_CONNECTION_GET_PRIVATE(connection),
+                                                         (addr_family == AF_INET)
+                                                             ? NM_META_SETTING_TYPE_IP4_CONFIG
+                                                             : NM_META_SETTING_TYPE_IP6_CONFIG));
 }
 
 /**
@@ -363,12 +354,14 @@ nm_connection_get_setting_ip_config(NMConnection *connection, int addr_family)
 NMSetting *
 nm_connection_get_setting_by_name(NMConnection *connection, const char *name)
 {
-    GType type;
+    const NMMetaSettingInfo *setting_info;
 
     g_return_val_if_fail(NM_IS_CONNECTION(connection), NULL);
 
-    type = nm_setting_lookup_type(name);
-    return type ? _connection_get_setting(connection, type) : NULL;
+    setting_info = nm_meta_setting_infos_by_name(name);
+    return setting_info ? _get_setting_by_metatype(NM_CONNECTION_GET_PRIVATE(connection),
+                                                   setting_info->meta_type)
+                        : NULL;
 }
 
 /*****************************************************************************/
@@ -764,10 +757,11 @@ diff_one_connection(NMConnection         *a,
  * @a: a #NMConnection
  * @b: a second #NMConnection to compare with the first
  * @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT
- * @out_settings: (element-type utf8 GLib.HashTable): if the
- * connections differ, on return a hash table mapping setting names to
- * second-level GHashTable (utf8 to guint32), which contains the key names that
- * differ mapped to one or more of %NMSettingDiffResult as a bitfield
+ * @out_settings: (out) (element-type utf8 GLib.HashTable(utf8,uint32)) (optional) (nullable):
+ * if the connections differ, on return a hash table
+ * mapping setting names to second-level GHashTable (utf8 to guint32), which
+ * contains the key names that differ mapped to one or more of
+ * %NMSettingDiffResult as a bitfield
  *
  * Compares two #NMConnection objects for similarity, with comparison behavior
  * modified by a set of flags.  See nm_setting_compare() for a description of
@@ -882,6 +876,20 @@ _normalize_connection_uuid(NMConnection *self)
     return TRUE;
 }
 
+static gboolean
+_normalize_connection(NMConnection *self)
+{
+    NMSettingConnection *s_con   = nm_connection_get_setting_connection(self);
+    gboolean             changed = FALSE;
+
+    if (nm_setting_connection_get_read_only(s_con)) {
+        g_object_set(s_con, NM_SETTING_CONNECTION_READ_ONLY, FALSE, NULL);
+        changed = TRUE;
+    }
+
+    return changed;
+}
+
 gboolean
 _nm_setting_connection_verify_secondaries(GArray *secondaries, GError **error)
 {
@@ -1471,6 +1479,28 @@ _normalize_wireless_mac_address_randomization(NMConnection *self)
 }
 
 static gboolean
+_normalize_wireless(NMConnection *self)
+{
+    NMSettingWireless *s_wifi  = nm_connection_get_setting_wireless(self);
+    gboolean           changed = FALSE;
+
+    if (!s_wifi)
+        return FALSE;
+
+    if (nm_setting_wireless_get_rate(s_wifi) != 0) {
+        g_object_set(s_wifi, NM_SETTING_WIRELESS_RATE, 0u, NULL);
+        changed = TRUE;
+    }
+
+    if (nm_setting_wireless_get_tx_power(s_wifi) != 0) {
+        g_object_set(s_wifi, NM_SETTING_WIRELESS_TX_POWER, 0u, NULL);
+        changed = TRUE;
+    }
+
+    return changed;
+}
+
+static gboolean
 _normalize_macsec(NMConnection *self)
 {
     NMSettingMacsec *s_macsec = nm_connection_get_setting_macsec(self);
@@ -1658,8 +1688,8 @@ _normalize_802_1x_empty_strings(NMConnection *self)
     NMSetting8021x *s_8021x;
     gboolean        changed = FALSE;
 
-    s_8021x = _connection_get_setting_by_meta_type(NM_CONNECTION_GET_PRIVATE(self),
-                                                   NM_META_SETTING_TYPE_802_1X);
+    s_8021x =
+        _get_setting_by_metatype(NM_CONNECTION_GET_PRIVATE(self), NM_META_SETTING_TYPE_802_1X);
     if (!s_8021x)
         return FALSE;
 
@@ -1809,7 +1839,7 @@ _nm_connection_verify(NMConnection *connection, GError **error)
 
     priv = NM_CONNECTION_GET_PRIVATE(connection);
 
-    if (!_connection_get_setting_by_meta_type(priv, NM_META_SETTING_TYPE_CONNECTION)) {
+    if (!_get_setting_by_metatype(priv, NM_META_SETTING_TYPE_CONNECTION)) {
         g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_MISSING_SETTING,
@@ -1854,9 +1884,9 @@ _nm_connection_verify(NMConnection *connection, GError **error)
         g_clear_error(&verify_error);
     }
 
-    s_ip4   = _connection_get_setting_by_meta_type(priv, NM_META_SETTING_TYPE_IP4_CONFIG);
-    s_ip6   = _connection_get_setting_by_meta_type(priv, NM_META_SETTING_TYPE_IP6_CONFIG);
-    s_proxy = _connection_get_setting_by_meta_type(priv, NM_META_SETTING_TYPE_PROXY);
+    s_ip4   = _get_setting_by_metatype(priv, NM_META_SETTING_TYPE_IP4_CONFIG);
+    s_ip6   = _get_setting_by_metatype(priv, NM_META_SETTING_TYPE_IP6_CONFIG);
+    s_proxy = _get_setting_by_metatype(priv, NM_META_SETTING_TYPE_PROXY);
 
     nm_assert(normalizable_error_type != NM_SETTING_VERIFY_ERROR);
     if (NM_IN_SET(normalizable_error_type,
@@ -2005,6 +2035,7 @@ _connection_normalize(NMConnection *connection,
     was_modified |= _normalize_connection_type(connection);
     was_modified |= _normalize_connection_slave_type(connection);
     was_modified |= _normalize_connection_secondaries(connection);
+    was_modified |= _normalize_connection(connection);
     was_modified |= _normalize_required_settings(connection);
     was_modified |= _normalize_invalid_slave_port_settings(connection);
     was_modified |= _normalize_ip_config(connection, parameters);
@@ -2013,6 +2044,7 @@ _connection_normalize(NMConnection *connection,
     was_modified |= _normalize_bond_mode(connection);
     was_modified |= _normalize_bond_options(connection);
     was_modified |= _normalize_wireless_mac_address_randomization(connection);
+    was_modified |= _normalize_wireless(connection);
     was_modified |= _normalize_macsec(connection);
     was_modified |= _normalize_team_config(connection);
     was_modified |= _normalize_team_port_config(connection);
@@ -2057,12 +2089,12 @@ _connection_normalize(NMConnection *connection,
 /**
  * nm_connection_normalize:
  * @connection: the #NMConnection to normalize
- * @parameters: (allow-none) (element-type utf8 gpointer): a #GHashTable with
+ * @parameters: (nullable) (element-type utf8 gpointer): a #GHashTable with
  * normalization parameters to allow customization of the normalization by providing
  * specific arguments. Unknown arguments will be ignored and the default will be
  * used. The keys must be strings compared with g_str_equal() function.
  * The values are opaque and depend on the parameter name.
- * @modified: (out) (allow-none): outputs whether any settings were modified.
+ * @modified: (out) (optional): outputs whether any settings were modified.
  * @error: location to store error, or %NULL. Contains the reason,
  * why the connection is invalid, if the function returns an error.
  *
@@ -2413,7 +2445,7 @@ _need_secrets(NMConnection *connection, gboolean check_rerequest, GPtrArray **hi
 /**
  * nm_connection_need_secrets:
  * @connection: the #NMConnection
- * @hints: (out) (element-type utf8) (allow-none) (transfer container):
+ * @hints: (out) (element-type utf8) (nullable) (transfer container):
  *   the address of a pointer to a #GPtrArray, initialized to %NULL, which on
  *   return points to an allocated #GPtrArray containing the property names of
  *   secrets of the #NMSetting which may be required; the caller owns the array
@@ -2426,8 +2458,8 @@ _need_secrets(NMConnection *connection, gboolean check_rerequest, GPtrArray **hi
  * circumstances, there is no way to conclusively determine exactly which
  * secrets are needed.
  *
- * Returns: the setting name of the #NMSetting object which has invalid or
- *   missing secrets
+ * Returns: (nullable): the setting name of the #NMSetting object which has
+ *   invalid or missing secrets
  **/
 const char *
 nm_connection_need_secrets(NMConnection *connection, GPtrArray **hints)
@@ -2442,7 +2474,7 @@ nm_connection_need_secrets(NMConnection *connection, GPtrArray **hints)
  * nm_connection_need_secrets_for_rerequest:
  * @connection: the #NMConnection
  *
- * Returns TRUE if some secret needs to be re-requested
+ * Returns: %TRUE if some secret needs to be re-requested
  **/
 gboolean
 nm_connection_need_secrets_for_rerequest(NMConnection *connection)
@@ -2468,7 +2500,7 @@ nm_connection_clear_secrets(NMConnection *connection)
 /**
  * nm_connection_clear_secrets_with_flags:
  * @connection: the #NMConnection
- * @func: (scope call) (allow-none): function to be called to determine whether a
+ * @func: (scope call) (nullable): function to be called to determine whether a
  *     specific secret should be cleared or not. If %NULL, all secrets are cleared.
  * @user_data: caller-supplied data passed to @func
  *
@@ -2780,15 +2812,14 @@ _nm_connection_get_settings_arr(NMConnection *connection)
 /**
  * nm_connection_get_settings:
  * @connection: the #NMConnection instance
- * @out_length: (allow-none) (out): the length of the returned array
+ * @out_length: (out) (optional): the length of the returned array
  *
  * Retrieves the settings in @connection.
  *
  * The returned array is %NULL-terminated.
  *
- * Returns: (array length=out_length) (transfer container): a
- *   %NULL-terminated array containing every setting of
- *   @connection.
+ * Returns: (array length=out_length) (transfer container) (nullable): a
+ *   %NULL-terminated array containing every setting of @connection.
  *   If the connection has no settings, %NULL is returned.
  *
  * Since: 1.10
@@ -3247,7 +3278,7 @@ nm_connection_get_virtual_device_description(NMConnection *connection)
 NMSetting8021x *
 nm_connection_get_setting_802_1x(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_802_1X);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_802_1X);
 }
 
 /**
@@ -3261,7 +3292,7 @@ nm_connection_get_setting_802_1x(NMConnection *connection)
 NMSettingBluetooth *
 nm_connection_get_setting_bluetooth(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_BLUETOOTH);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_BLUETOOTH);
 }
 
 /**
@@ -3275,7 +3306,7 @@ nm_connection_get_setting_bluetooth(NMConnection *connection)
 NMSettingBond *
 nm_connection_get_setting_bond(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_BOND);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_BOND);
 }
 
 /**
@@ -3289,7 +3320,7 @@ nm_connection_get_setting_bond(NMConnection *connection)
 NMSettingTeam *
 nm_connection_get_setting_team(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_TEAM);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_TEAM);
 }
 
 /**
@@ -3303,7 +3334,7 @@ nm_connection_get_setting_team(NMConnection *connection)
 NMSettingTeamPort *
 nm_connection_get_setting_team_port(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_TEAM_PORT);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_TEAM_PORT);
 }
 
 /**
@@ -3317,7 +3348,7 @@ nm_connection_get_setting_team_port(NMConnection *connection)
 NMSettingBridge *
 nm_connection_get_setting_bridge(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_BRIDGE);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_BRIDGE);
 }
 
 /**
@@ -3331,7 +3362,7 @@ nm_connection_get_setting_bridge(NMConnection *connection)
 NMSettingCdma *
 nm_connection_get_setting_cdma(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_CDMA);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_CDMA);
 }
 
 /**
@@ -3345,7 +3376,7 @@ nm_connection_get_setting_cdma(NMConnection *connection)
 NMSettingConnection *
 nm_connection_get_setting_connection(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_CONNECTION);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_CONNECTION);
 }
 
 /**
@@ -3359,7 +3390,7 @@ nm_connection_get_setting_connection(NMConnection *connection)
 NMSettingDcb *
 nm_connection_get_setting_dcb(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_DCB);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_DCB);
 }
 
 /**
@@ -3375,7 +3406,7 @@ nm_connection_get_setting_dcb(NMConnection *connection)
 NMSettingDummy *
 nm_connection_get_setting_dummy(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_DUMMY);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_DUMMY);
 }
 
 /**
@@ -3389,7 +3420,7 @@ nm_connection_get_setting_dummy(NMConnection *connection)
 NMSettingGeneric *
 nm_connection_get_setting_generic(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_GENERIC);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_GENERIC);
 }
 
 /**
@@ -3403,7 +3434,7 @@ nm_connection_get_setting_generic(NMConnection *connection)
 NMSettingGsm *
 nm_connection_get_setting_gsm(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_GSM);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_GSM);
 }
 
 /**
@@ -3417,7 +3448,7 @@ nm_connection_get_setting_gsm(NMConnection *connection)
 NMSettingInfiniband *
 nm_connection_get_setting_infiniband(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_INFINIBAND);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_INFINIBAND);
 }
 
 /**
@@ -3436,7 +3467,7 @@ nm_connection_get_setting_infiniband(NMConnection *connection)
 NMSettingIPConfig *
 nm_connection_get_setting_ip4_config(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_IP4_CONFIG);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_IP4_CONFIG);
 }
 
 /**
@@ -3452,7 +3483,7 @@ nm_connection_get_setting_ip4_config(NMConnection *connection)
 NMSettingIPTunnel *
 nm_connection_get_setting_ip_tunnel(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_IP_TUNNEL);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_IP_TUNNEL);
 }
 
 /**
@@ -3471,7 +3502,7 @@ nm_connection_get_setting_ip_tunnel(NMConnection *connection)
 NMSettingIPConfig *
 nm_connection_get_setting_ip6_config(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_IP6_CONFIG);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_IP6_CONFIG);
 }
 
 /**
@@ -3487,7 +3518,7 @@ nm_connection_get_setting_ip6_config(NMConnection *connection)
 NMSettingMacsec *
 nm_connection_get_setting_macsec(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_MACSEC);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_MACSEC);
 }
 
 /**
@@ -3503,7 +3534,7 @@ nm_connection_get_setting_macsec(NMConnection *connection)
 NMSettingMacvlan *
 nm_connection_get_setting_macvlan(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_MACVLAN);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_MACVLAN);
 }
 
 /**
@@ -3517,7 +3548,7 @@ nm_connection_get_setting_macvlan(NMConnection *connection)
 NMSettingOlpcMesh *
 nm_connection_get_setting_olpc_mesh(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_OLPC_MESH);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_OLPC_MESH);
 }
 
 /**
@@ -3533,7 +3564,7 @@ nm_connection_get_setting_olpc_mesh(NMConnection *connection)
 NMSettingOvsBridge *
 nm_connection_get_setting_ovs_bridge(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_OVS_BRIDGE);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_OVS_BRIDGE);
 }
 
 /**
@@ -3549,8 +3580,7 @@ nm_connection_get_setting_ovs_bridge(NMConnection *connection)
 NMSettingOvsInterface *
 nm_connection_get_setting_ovs_interface(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection,
-                                                      NM_META_SETTING_TYPE_OVS_INTERFACE);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_OVS_INTERFACE);
 }
 
 /**
@@ -3566,7 +3596,7 @@ nm_connection_get_setting_ovs_interface(NMConnection *connection)
 NMSettingOvsPatch *
 nm_connection_get_setting_ovs_patch(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_OVS_PATCH);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_OVS_PATCH);
 }
 
 /**
@@ -3582,7 +3612,7 @@ nm_connection_get_setting_ovs_patch(NMConnection *connection)
 NMSettingOvsPort *
 nm_connection_get_setting_ovs_port(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_OVS_PORT);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_OVS_PORT);
 }
 
 /**
@@ -3596,7 +3626,7 @@ nm_connection_get_setting_ovs_port(NMConnection *connection)
 NMSettingPpp *
 nm_connection_get_setting_ppp(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_PPP);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_PPP);
 }
 
 /**
@@ -3610,7 +3640,7 @@ nm_connection_get_setting_ppp(NMConnection *connection)
 NMSettingPppoe *
 nm_connection_get_setting_pppoe(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_PPPOE);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_PPPOE);
 }
 
 /**
@@ -3626,7 +3656,7 @@ nm_connection_get_setting_pppoe(NMConnection *connection)
 NMSettingProxy *
 nm_connection_get_setting_proxy(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_PROXY);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_PROXY);
 }
 
 /**
@@ -3640,7 +3670,7 @@ nm_connection_get_setting_proxy(NMConnection *connection)
 NMSettingSerial *
 nm_connection_get_setting_serial(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_SERIAL);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_SERIAL);
 }
 
 /**
@@ -3656,7 +3686,7 @@ nm_connection_get_setting_serial(NMConnection *connection)
 NMSettingTCConfig *
 nm_connection_get_setting_tc_config(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_TC_CONFIG);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_TC_CONFIG);
 }
 
 /**
@@ -3672,7 +3702,7 @@ nm_connection_get_setting_tc_config(NMConnection *connection)
 NMSettingTun *
 nm_connection_get_setting_tun(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_TUN);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_TUN);
 }
 
 /**
@@ -3686,7 +3716,7 @@ nm_connection_get_setting_tun(NMConnection *connection)
 NMSettingVpn *
 nm_connection_get_setting_vpn(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_VPN);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_VPN);
 }
 
 /**
@@ -3702,7 +3732,7 @@ nm_connection_get_setting_vpn(NMConnection *connection)
 NMSettingVxlan *
 nm_connection_get_setting_vxlan(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_VXLAN);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_VXLAN);
 }
 
 /**
@@ -3716,7 +3746,7 @@ nm_connection_get_setting_vxlan(NMConnection *connection)
 NMSettingWimax *
 nm_connection_get_setting_wimax(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_WIMAX);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_WIMAX);
 }
 
 /**
@@ -3730,7 +3760,7 @@ nm_connection_get_setting_wimax(NMConnection *connection)
 NMSettingWired *
 nm_connection_get_setting_wired(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_WIRED);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_WIRED);
 }
 
 /**
@@ -3744,7 +3774,7 @@ nm_connection_get_setting_wired(NMConnection *connection)
 NMSettingAdsl *
 nm_connection_get_setting_adsl(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_ADSL);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_ADSL);
 }
 
 /**
@@ -3758,7 +3788,7 @@ nm_connection_get_setting_adsl(NMConnection *connection)
 NMSettingWireless *
 nm_connection_get_setting_wireless(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_WIRELESS);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_WIRELESS);
 }
 
 /**
@@ -3772,8 +3802,8 @@ nm_connection_get_setting_wireless(NMConnection *connection)
 NMSettingWirelessSecurity *
 nm_connection_get_setting_wireless_security(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection,
-                                                      NM_META_SETTING_TYPE_WIRELESS_SECURITY);
+    return _nm_connection_get_setting_by_metatype(connection,
+                                                  NM_META_SETTING_TYPE_WIRELESS_SECURITY);
 }
 
 /**
@@ -3787,7 +3817,7 @@ nm_connection_get_setting_wireless_security(NMConnection *connection)
 NMSettingBridgePort *
 nm_connection_get_setting_bridge_port(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_BRIDGE_PORT);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_BRIDGE_PORT);
 }
 
 /**
@@ -3801,7 +3831,7 @@ nm_connection_get_setting_bridge_port(NMConnection *connection)
 NMSettingVlan *
 nm_connection_get_setting_vlan(NMConnection *connection)
 {
-    return _connection_get_setting_by_meta_type_check(connection, NM_META_SETTING_TYPE_VLAN);
+    return _nm_connection_get_setting_by_metatype(connection, NM_META_SETTING_TYPE_VLAN);
 }
 
 NMSettingBluetooth *
diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c
index 9a6ffc6f..82581d9f 100644
--- a/src/libnm-core-impl/nm-keyfile.c
+++ b/src/libnm-core-impl/nm-keyfile.c
@@ -3911,9 +3911,9 @@ nm_keyfile_read_ensure_uuid(NMConnection *connection, const char *fallback_uuid_
  *   the relative path is made absolute using @base_dir. This must
  *   be an absolute path.
  * @handler_flags: the #NMKeyfileHandlerFlags.
- * @handler: (allow-none) (scope call): read handler
+ * @handler: (nullable) (scope call): read handler
  * @user_data: user data for read handler
- * @error: (allow-none) (out): error
+ * @error: error
  *
  * Tries to create a NMConnection from a keyfile. The resulting keyfile is
  * not normalized and might not even verify.
@@ -4256,7 +4256,7 @@ _write_setting_wireguard(NMSetting *setting, KeyfileWriterInfo *info)
  * nm_keyfile_write:
  * @connection: the #NMConnection to persist to keyfile.
  * @handler_flags: the #NMKeyfileHandlerFlags.
- * @handler: (allow-none) (scope call): optional handler for events and
+ * @handler: (nullable) (scope call): optional handler for events and
  *   to override the default behavior.
  * @user_data: argument for @handler.
  * @error: the #GError in case writing fails.
@@ -4554,13 +4554,14 @@ nm_keyfile_handler_data_fail_with_error(NMKeyfileHandlerData *handler_data, GErr
 /**
  * nm_keyfile_handler_data_get_context:
  * @handler_data: the #NMKeyfileHandlerData for any event.
- * @out_kf_group_name: (out) (allow-none) (transfer none): if the event is in the
- *   context of a keyfile group, the group name.
- * @out_kf_key_name: (out) (allow-none) (transfer none): if the event is in the
- *   context of a keyfile value, the key name.
- * @out_cur_setting: (out) (allow-none) (transfer none): if the event happens while
- *   handling a particular #NMSetting instance.
- * @out_cur_property_name: (out) (allow-none) (transfer none): the property name if applicable.
+ * @out_kf_group_name: (out) (optional) (nullable) (transfer none): if the event
+ *   is in the context of a keyfile group, the group name.
+ * @out_kf_key_name: (out) (optional) (nullable) (transfer none): if the event
+ *   is in the context of a keyfile value, the key name.
+ * @out_cur_setting: (out) (optional) (nullable) (transfer none): if the event
+ *   happens while handling a particular #NMSetting instance.
+ * @out_cur_property_name: (out) (optional) (nullable) (transfer none): the
+ *   property name if applicable.
  *
  * Get context information of the current event. This function can be called
  * on all events, but the context information may be unset.
@@ -4605,8 +4606,8 @@ _nm_keyfile_handler_data_warn_get_message(const NMKeyfileHandlerData *handler_da
  * nm_keyfile_handler_data_warn_get:
  * @handler_data: the #NMKeyfileHandlerData for a %NM_KEYFILE_HANDLER_TYPE_WARN
  *  event.
- * @out_message: (out) (allow-none) (transfer none): the warning message.
- * @out_severity: (out) (allow-none): the #NMKeyfileWarnSeverity warning severity.
+ * @out_message: (out) (optional) (transfer none): the warning message.
+ * @out_severity: (out) (optional): the #NMKeyfileWarnSeverity warning severity.
  *
  * Since: 1.30
  */
diff --git a/src/libnm-core-impl/nm-meta-setting-base-impl.c b/src/libnm-core-impl/nm-meta-setting-base-impl.c
index 19082671..b531ae85 100644
--- a/src/libnm-core-impl/nm-meta-setting-base-impl.c
+++ b/src/libnm-core-impl/nm-meta-setting-base-impl.c
@@ -34,6 +34,7 @@
 #include "nm-setting-ip-tunnel.h"
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-ip6-config.h"
+#include "nm-setting-link.h"
 #include "nm-setting-loopback.h"
 #include "nm-setting-macsec.h"
 #include "nm-setting-macvlan.h"
@@ -362,6 +363,13 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
             .setting_name      = NM_SETTING_IP_TUNNEL_SETTING_NAME,
             .get_setting_gtype = nm_setting_ip_tunnel_get_type,
         },
+    [NM_META_SETTING_TYPE_LINK] =
+        {
+            .meta_type         = NM_META_SETTING_TYPE_LINK,
+            .setting_priority  = NM_SETTING_PRIORITY_AUX,
+            .setting_name      = NM_SETTING_LINK_SETTING_NAME,
+            .get_setting_gtype = nm_setting_link_get_type,
+        },
     [NM_META_SETTING_TYPE_LOOPBACK] =
         {
             .meta_type         = NM_META_SETTING_TYPE_LOOPBACK,
@@ -660,6 +668,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = {
     NM_META_SETTING_TYPE_BOND_PORT,
     NM_META_SETTING_TYPE_BRIDGE_PORT,
     NM_META_SETTING_TYPE_ETHTOOL,
+    NM_META_SETTING_TYPE_LINK,
     NM_META_SETTING_TYPE_MATCH,
     NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS,
     NM_META_SETTING_TYPE_OVS_OTHER_CONFIG,
diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c
index 1bc4de9d..626673e8 100644
--- a/src/libnm-core-impl/nm-setting-8021x.c
+++ b/src/libnm-core-impl/nm-setting-8021x.c
@@ -208,9 +208,9 @@ G_DEFINE_TYPE(NMSetting8021x, nm_setting_802_1x, NM_TYPE_SETTING)
 
 /**
  * nm_setting_802_1x_check_cert_scheme:
- * @pdata: (allow-none): the data pointer
+ * @pdata: (nullable): the data pointer
  * @length: the length of the data
- * @error: (allow-none) (out): validation reason
+ * @error: validation reason
  *
  * Determines and verifies the blob type.
  * When setting certificate properties of NMSetting8021x
diff --git a/src/libnm-core-impl/nm-setting-bond-port.c b/src/libnm-core-impl/nm-setting-bond-port.c
index 16512a6a..e8917902 100644
--- a/src/libnm-core-impl/nm-setting-bond-port.c
+++ b/src/libnm-core-impl/nm-setting-bond-port.c
@@ -72,7 +72,7 @@ nm_setting_bond_port_get_queue_id(NMSettingBondPort *setting)
  *
  * Returns: the #NMSettingBondPort:prio property of the setting
  *
- * Since: 1.44, 1.42.8
+ * Since: 1.44
  **/
 gint32
 nm_setting_bond_port_get_prio(NMSettingBondPort *setting)
@@ -190,7 +190,7 @@ nm_setting_bond_port_class_init(NMSettingBondPortClass *klass)
      * the highest priority. This option is only compatible with active-backup,
      * balance-tlb and balance-alb modes.
      *
-     * Since: 1.44, 1.42.8
+     * Since: 1.44
      **/
     /* ---ifcfg-rh---
      * property: prio
diff --git a/src/libnm-core-impl/nm-setting-bond.c b/src/libnm-core-impl/nm-setting-bond.c
index 54030e0b..81c67d51 100644
--- a/src/libnm-core-impl/nm-setting-bond.c
+++ b/src/libnm-core-impl/nm-setting-bond.c
@@ -92,6 +92,9 @@ static const char *const valid_options_lst[] = {
     NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,
     NM_SETTING_BOND_OPTION_LP_INTERVAL,
     NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY,
+    NM_SETTING_BOND_OPTION_ARP_MISSED_MAX,
+    NM_SETTING_BOND_OPTION_LACP_ACTIVE,
+    NM_SETTING_BOND_OPTION_NS_IP6_TARGET,
     NULL,
 };
 
@@ -133,6 +136,7 @@ _nm_assert_bond_meta(const OptionMeta *option_meta)
         }));
         return TRUE;
     case NM_BOND_OPTION_TYPE_IP:
+    case NM_BOND_OPTION_TYPE_IP6:
         nm_assert(option_meta->val);
         /* fall-through */
     case NM_BOND_OPTION_TYPE_IFNAME:
@@ -154,8 +158,9 @@ static char const *const _option_default_strv_arp_validate[] =
     NM_MAKE_STRV("none", "active", "backup", "all", "filter", "filter_active", "filter_backup");
 static char const *const _option_default_strv_fail_over_mac[] =
     NM_MAKE_STRV("none", "active", "follow");
-static char const *const _option_default_strv_lacp_rate[] = NM_MAKE_STRV("slow", "fast");
-static char const *const _option_default_strv_mode[]      = NM_MAKE_STRV("balance-rr",
+static char const *const _option_default_strv_lacp_rate[]   = NM_MAKE_STRV("slow", "fast");
+static char const *const _option_default_strv_lacp_active[] = NM_MAKE_STRV("off", "on");
+static char const *const _option_default_strv_mode[]        = NM_MAKE_STRV("balance-rr",
                                                                     "active-backup",
                                                                     "balance-xor",
                                                                     "broadcast",
@@ -194,12 +199,15 @@ static NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(
      {"any", NM_BOND_OPTION_TYPE_BOTH, 0, 1, _option_default_strv_arp_all_targets}},
     {NM_SETTING_BOND_OPTION_ARP_INTERVAL, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}},
     {NM_SETTING_BOND_OPTION_ARP_IP_TARGET, {"", NM_BOND_OPTION_TYPE_IP}},
+    {NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, {"0", NM_BOND_OPTION_TYPE_INT, 0, 255}},
     {NM_SETTING_BOND_OPTION_ARP_VALIDATE,
      {"none", NM_BOND_OPTION_TYPE_BOTH, 0, 6, _option_default_strv_arp_validate}},
     {NM_SETTING_BOND_OPTION_BALANCE_SLB, {"0", NM_BOND_OPTION_TYPE_INT, 0, 1}},
     {NM_SETTING_BOND_OPTION_DOWNDELAY, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}},
     {NM_SETTING_BOND_OPTION_FAIL_OVER_MAC,
      {"none", NM_BOND_OPTION_TYPE_BOTH, 0, 2, _option_default_strv_fail_over_mac}},
+    {NM_SETTING_BOND_OPTION_LACP_ACTIVE,
+     {"on", NM_BOND_OPTION_TYPE_BOTH, 0, 1, _option_default_strv_lacp_active}},
     {NM_SETTING_BOND_OPTION_LACP_RATE,
      {"slow", NM_BOND_OPTION_TYPE_BOTH, 0, 1, _option_default_strv_lacp_rate}},
     {NM_SETTING_BOND_OPTION_LP_INTERVAL, {"1", NM_BOND_OPTION_TYPE_INT, 1, G_MAXINT}},
@@ -207,6 +215,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(
     {NM_SETTING_BOND_OPTION_MIN_LINKS, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}},
     {NM_SETTING_BOND_OPTION_MODE,
      {"balance-rr", NM_BOND_OPTION_TYPE_BOTH, 0, 6, _option_default_strv_mode}},
+    {NM_SETTING_BOND_OPTION_NS_IP6_TARGET, {"", NM_BOND_OPTION_TYPE_IP6}},
     {NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, {"1", NM_BOND_OPTION_TYPE_INT, 0, 255}},
     {NM_SETTING_BOND_OPTION_NUM_UNSOL_NA, {"1", NM_BOND_OPTION_TYPE_INT, 0, 255}},
     {NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, {"1", NM_BOND_OPTION_TYPE_INT, 0, 65535}},
@@ -504,12 +513,12 @@ validate_list(const char *name, const char *value, const OptionMeta *option_meta
 }
 
 static gboolean
-validate_ip(const char *name, const char *value, GError **error)
+validate_ip(int addr_family, const char *name, const char *value, GError **error)
 {
     gs_free const char **addrs = NULL;
     gsize                i;
 
-    addrs = nm_utils_bond_option_arp_ip_targets_split(value);
+    addrs = nm_utils_bond_option_ip_split(value);
     if (!addrs) {
         g_set_error(error,
                     NM_CONNECTION_ERROR,
@@ -518,13 +527,18 @@ validate_ip(const char *name, const char *value, GError **error)
                     name);
         return FALSE;
     }
+
+    /* An empty list is invalid. */
+    nm_assert(addrs[0]);
+
     for (i = 0; addrs[i]; i++) {
-        if (!nm_inet_parse_bin(AF_INET, addrs[i], NULL, NULL)) {
+        if (!nm_inet_parse_bin(addr_family, addrs[i], NULL, NULL)) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                        _("'%s' is not a valid IPv4 address for '%s' option"),
+                        _("'%s' is not a valid %s address for '%s' option"),
                         addrs[i],
+                        addr_family == AF_INET ? "IPv4" : "IPv6",
                         name);
             return FALSE;
         }
@@ -574,7 +588,10 @@ _nm_setting_bond_validate_option(const char *name, const char *value, GError **e
         goto handle_error;
     case NM_BOND_OPTION_TYPE_IP:
         nm_assert(nm_streq0(name, NM_SETTING_BOND_OPTION_ARP_IP_TARGET));
-        return validate_ip(name, value, error);
+        return validate_ip(AF_INET, name, value, error);
+    case NM_BOND_OPTION_TYPE_IP6:
+        nm_assert(nm_streq0(name, NM_SETTING_BOND_OPTION_NS_IP6_TARGET));
+        return validate_ip(AF_INET6, name, value, error);
     case NM_BOND_OPTION_TYPE_MAC:
         success = nm_utils_hwaddr_valid(value, ETH_ALEN);
         goto handle_error;
@@ -601,7 +618,7 @@ handle_error:
 /**
  * nm_setting_bond_validate_option:
  * @name: the name of the option to validate
- * @value: (allow-none): the value of the option to validate.
+ * @value: (nullable): the value of the option to validate.
  *
  * Checks whether @name is a valid bond option and @value is a valid value for
  * the @name. If @value is %NULL, the function only validates the option name.
@@ -703,13 +720,13 @@ nm_setting_bond_remove_option(NMSettingBond *setting, const char *name)
 
 /**
  * nm_setting_bond_get_valid_options:
- * @setting: (allow-none): the #NMSettingBond
+ * @setting: (nullable): the #NMSettingBond
  *
  * Returns a list of valid bond options.
  *
  * The @setting argument is unused and may be passed as %NULL.
  *
- * Returns: (transfer none): a %NULL-terminated array of strings of valid bond options.
+ * Returns: (transfer none) (nullable): a %NULL-terminated array of strings of valid bond options.
  **/
 const char **
 nm_setting_bond_get_valid_options(NMSettingBond *setting)
@@ -846,12 +863,15 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     NMSettingBondPrivate    *priv = NM_SETTING_BOND_GET_PRIVATE(setting);
     int                      miimon;
     int                      arp_interval;
+    int                      arp_missed_max;
     int                      num_grat_arp;
     int                      num_unsol_na;
     int                      peer_notif_delay;
     const char              *mode_str;
     const char              *arp_ip_target = NULL;
+    const char              *ns_ip6_target;
     const char              *lacp_rate;
+    const char              *lacp_active;
     const char              *primary;
     const char              *s;
     NMBondMode               bond_mode;
@@ -876,6 +896,8 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
 
     miimon       = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_MIIMON));
     arp_interval = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_ARP_INTERVAL));
+    arp_missed_max =
+        _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX));
     num_grat_arp = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP));
     num_unsol_na = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA));
     peer_notif_delay =
@@ -886,6 +908,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
      * arp_interval conflicts [ alb, tlb ]
      * arp_interval needs arp_ip_target
      * arp_validate does not work with [ BOND_MODE_8023AD, BOND_MODE_TLB, BOND_MODE_ALB ]
+     * arp_missed_max does not work with [ BOND_MODE_8023AD, BOND_MODE_TLB, BOND_MODE_ALB ]
      * downdelay needs miimon
      * updelay needs miimon
      * peer_notif_delay needs miimon enabled
@@ -929,6 +952,17 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
             return FALSE;
         }
+        if (arp_missed_max > 0) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s=%s' is incompatible with '%s > 0'"),
+                        NM_SETTING_BOND_OPTION_MODE,
+                        mode_str,
+                        NM_SETTING_BOND_OPTION_ARP_MISSED_MAX);
+            g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+            return FALSE;
+        }
     }
 
     primary = _bond_get_option(self, NM_SETTING_BOND_OPTION_PRIMARY);
@@ -1028,18 +1062,20 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
-    /* arp_ip_target can only be used with arp_interval, and must
+    /* arp_ip_target and ns_ip6_target can only be used with arp_interval, and must
      * contain a comma-separated list of IPv4 addresses.
      */
+    ns_ip6_target = _bond_get_option(self, NM_SETTING_BOND_OPTION_NS_IP6_TARGET);
     arp_ip_target = _bond_get_option(self, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
     if (arp_interval > 0) {
-        if (!arp_ip_target) {
+        if (!arp_ip_target && !ns_ip6_target) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                        _("'%s' option requires '%s' option to be set"),
+                        _("'%s' option requires '%s' or '%s'option to be set"),
                         NM_SETTING_BOND_OPTION_ARP_INTERVAL,
-                        NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
+                        NM_SETTING_BOND_OPTION_ARP_IP_TARGET,
+                        NM_SETTING_BOND_OPTION_NS_IP6_TARGET);
             g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
             return FALSE;
         }
@@ -1054,6 +1090,16 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
             return FALSE;
         }
+        if (ns_ip6_target) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' option requires '%s' option to be set"),
+                        NM_SETTING_BOND_OPTION_NS_IP6_TARGET,
+                        NM_SETTING_BOND_OPTION_ARP_INTERVAL);
+            g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+            return FALSE;
+        }
     }
 
     lacp_rate = _bond_get_option(self, NM_SETTING_BOND_OPTION_LACP_RATE);
@@ -1068,6 +1114,18 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         return FALSE;
     }
 
+    lacp_active = _bond_get_option(self, NM_SETTING_BOND_OPTION_LACP_ACTIVE);
+    if (lacp_active && bond_mode != NM_BOND_MODE_8023AD) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("'%s' option is only valid with mode '%s'"),
+                    NM_SETTING_BOND_OPTION_LACP_RATE,
+                    "802.3ad");
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+        return FALSE;
+    }
+
     if (_bond_get_option(self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP)
         && _bond_get_option(self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA)
         && num_grat_arp != num_unsol_na) {
diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c
index 0ec36a89..3c26ab5d 100644
--- a/src/libnm-core-impl/nm-setting-connection.c
+++ b/src/libnm-core-impl/nm-setting-connection.c
@@ -398,7 +398,7 @@ nm_setting_connection_permissions_user_allowed(NMSettingConnection *setting, con
  * @setting: the #NMSettingConnection
  * @ptype: the permission type; at this time only "user" is supported
  * @pitem: the permission item formatted as required for @ptype
- * @detail: (allow-none): unused at this time; must be %NULL
+ * @detail: (nullable): unused at this time; must be %NULL
  *
  * Adds a permission to the connection's permission list.  At this time, only
  * the "user" permission type is supported, and @pitem must be a username. See
@@ -481,7 +481,7 @@ nm_setting_connection_remove_permission(NMSettingConnection *setting, guint32 id
  * @setting: the #NMSettingConnection
  * @ptype: the permission type; at this time only "user" is supported
  * @pitem: the permission item formatted as required for @ptype
- * @detail: (allow-none): unused at this time; must be %NULL
+ * @detail: (nullable): unused at this time; must be %NULL
  *
  * Removes the permission from the connection.
  * At this time, only the "user" permission type is supported, and @pitem must
@@ -647,6 +647,8 @@ _to_dbus_fcn_timestamp(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil)
  * Returns the #NMSettingConnection:read-only property of the connection.
  *
  * Returns: %TRUE if the connection is read-only, %FALSE if it is not
+ *
+ * Deprecated: 1.44: This property is deprecated and has no meaning.
  **/
 gboolean
 nm_setting_connection_get_read_only(NMSettingConnection *setting)
@@ -1608,6 +1610,18 @@ after_interface_name:
     if (!_nm_setting_connection_verify_secondaries(priv->secondaries.arr, error))
         return NM_SETTING_VERIFY_NORMALIZABLE;
 
+    if (priv->read_only) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                            _("read-only is deprecated and not settable for the user"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_CONNECTION_SETTING_NAME,
+                       NM_SETTING_CONNECTION_READ_ONLY);
+        return NM_SETTING_VERIFY_NORMALIZABLE;
+    }
+
     return TRUE;
 }
 
@@ -1883,12 +1897,13 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      * the stable-id can contain placeholders that are substituted dynamically and
      * deterministically depending on the context.
      *
-     * The stable-id is used for generating IPv6 stable private addresses
-     * with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the
-     * generated cloned MAC address for ethernet.cloned-mac-address=stable
-     * and wifi.cloned-mac-address=stable. It is also used as DHCP client
-     * identifier with ipv4.dhcp-client-id=stable and to derive the DHCP
-     * DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid].
+     * The stable-id is used for generating IPv6 stable private addresses with
+     * ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated
+     * cloned MAC address for ethernet.cloned-mac-address=stable and
+     * wifi.cloned-mac-address=stable. It is also used to derive the DHCP
+     * client identifier with ipv4.dhcp-client-id=stable, the DHCPv6 DUID with
+     * ipv6.dhcp-duid=stable-[llt,ll,uuid] and the DHCP IAID with
+     * ipv4.iaid=stable and ipv6.iaid=stable.
      *
      * Note that depending on the context where it is used, other parameters are
      * also seeded into the generation algorithm. For example, a per-host key
@@ -1898,22 +1913,21 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      * The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key.
      * See NetworkManager(8) manual about the secret-key and the host identity.
      *
-     * The '$' character is treated special to perform dynamic substitutions
-     * at runtime. Currently, supported are "${CONNECTION}", "${DEVICE}", "${MAC}",
-     * "${BOOT}", "${RANDOM}".
-     * These effectively create unique IDs per-connection, per-device, per-boot,
-     * or every time. Note that "${DEVICE}" corresponds to the interface name of the
-     * device and "${MAC}" is the permanent MAC address of the device.
+     * The '$' character is treated special to perform dynamic substitutions at
+     * activation time. Currently, supported are "${CONNECTION}", "${DEVICE}",
+     * "${MAC}", "${BOOT}", "${RANDOM}".  These effectively create unique IDs
+     * per-connection, per-device, per-boot, or every time. The "${CONNECTION}"
+     * uses the profile's connection.uuid, the "${DEVICE}" uses the interface
+     * name of the device and "${MAC}" the permanent MAC address of the device.
      * Any unrecognized patterns following '$' are treated verbatim, however
-     * are reserved for future use. You are thus advised to avoid '$' or
-     * escape it as "$$".
-     * For example, set it to "${CONNECTION}-${BOOT}-${DEVICE}" to create a unique id for
-     * this connection that changes with every reboot and differs depending on the
-     * interface where the profile activates.
+     * are reserved for future use. You are thus advised to avoid '$' or escape
+     * it as "$$".  For example, set it to "${CONNECTION}-${BOOT}-${DEVICE}" to
+     * create a unique id for this connection that changes with every reboot
+     * and differs depending on the interface where the profile activates.
      *
      * If the value is unset, a global connection default is consulted. If the
-     * value is still unset, the default is similar to "${CONNECTION}" and uses
-     * a unique, fixed ID for the connection.
+     * value is still unset, the default is "default${CONNECTION}" go generate
+     * an ID unique per connection profile.
      *
      * Since: 1.4
      **/
@@ -2190,9 +2204,9 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
     /**
      * NMSettingConnection:read-only:
      *
-     * %FALSE if the connection can be modified using the provided settings
-     * service's D-Bus interface with the right privileges, or %TRUE if the
-     * connection is read-only and cannot be modified.
+     * This property is deprecated and has no meaning.
+     *
+     * Deprecated: 1.44: This property is deprecated and has no meaning.
      **/
     _nm_setting_property_define_direct_boolean(properties_override,
                                                obj_properties,
@@ -2201,7 +2215,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                                                FALSE,
                                                NM_SETTING_PARAM_FUZZY_IGNORE,
                                                NMSettingConnectionPrivate,
-                                               read_only);
+                                               read_only,
+                                               .is_deprecated = TRUE, );
 
     /**
      * NMSettingConnection:zone:
diff --git a/src/libnm-core-impl/nm-setting-ethtool.c b/src/libnm-core-impl/nm-setting-ethtool.c
index d5199823..ab1ed0e2 100644
--- a/src/libnm-core-impl/nm-setting-ethtool.c
+++ b/src/libnm-core-impl/nm-setting-ethtool.c
@@ -25,7 +25,7 @@
 
 /**
  * nm_ethtool_optname_is_feature:
- * @optname: (allow-none): the option name to check
+ * @optname: (nullable): the option name to check
  *
  * Checks whether @optname is a valid option name for an offload feature.
  *
@@ -45,7 +45,7 @@ nm_ethtool_optname_is_feature(const char *optname)
 
 /**
  * nm_ethtool_optname_is_coalesce:
- * @optname: (allow-none): the option name to check
+ * @optname: (nullable): the option name to check
  *
  * Checks whether @optname is a valid option name for a coalesce setting.
  *
@@ -61,7 +61,7 @@ nm_ethtool_optname_is_coalesce(const char *optname)
 
 /**
  * nm_ethtool_optname_is_ring:
- * @optname: (allow-none): the option name to check
+ * @optname: (nullable): the option name to check
  *
  * Checks whether @optname is a valid option name for a ring setting.
  *
@@ -77,7 +77,7 @@ nm_ethtool_optname_is_ring(const char *optname)
 
 /**
  * nm_ethtool_optname_is_pause:
- * @optname: (allow-none): the option name to check
+ * @optname: (nullable): the option name to check
  *
  * Checks whether @optname is a valid option name for a pause setting.
  *
diff --git a/src/libnm-core-impl/nm-setting-gsm.c b/src/libnm-core-impl/nm-setting-gsm.c
index 3fe5dfa1..4dde515a 100644
--- a/src/libnm-core-impl/nm-setting-gsm.c
+++ b/src/libnm-core-impl/nm-setting-gsm.c
@@ -36,7 +36,9 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_AUTO_CONFIG,
                                   PROP_DEVICE_ID,
                                   PROP_SIM_ID,
                                   PROP_SIM_OPERATOR_ID,
-                                  PROP_MTU, );
+                                  PROP_MTU,
+                                  PROP_INITIAL_EPS_CONFIG,
+                                  PROP_INITIAL_EPS_APN, );
 
 typedef struct {
     char   *number;
@@ -48,11 +50,13 @@ typedef struct {
     char   *apn;
     char   *network_id;
     char   *pin;
+    char   *initial_eps_apn;
     guint   password_flags;
     guint   pin_flags;
     guint32 mtu;
     bool    auto_config;
     bool    home_only;
+    bool    initial_eps_config;
 } NMSettingGsmPrivate;
 
 /**
@@ -286,6 +290,103 @@ nm_setting_gsm_get_mtu(NMSettingGsm *setting)
     return NM_SETTING_GSM_GET_PRIVATE(setting)->mtu;
 }
 
+/**
+ * nm_setting_gsm_get_initial_eps_config:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: the #NMSettingGsm:initial-eps-bearer-configure property of the setting
+ *
+ * Since: 1.44
+ **/
+gboolean
+nm_setting_gsm_get_initial_eps_config(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_config;
+}
+
+/**
+ * nm_setting_gsm_get_initial_eps_apn:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: the #NMSettingGsm:initial-eps-bearer-apn property of the setting
+ *
+ * Since: 1.44
+ **/
+const char *
+nm_setting_gsm_get_initial_eps_apn(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_apn;
+}
+
+static gboolean
+_verify_apn(const char *apn, gboolean allow_empty, const char *property_name, GError **error)
+{
+    gsize apn_len;
+    gsize i;
+
+    if (!apn)
+        return TRUE;
+
+    apn_len = strlen(apn);
+
+    if (!allow_empty && apn_len == 0) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("property value is empty"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, property_name);
+        return FALSE;
+    }
+
+    if (apn_len > 64) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("property value is too long (>64)"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, property_name);
+        return FALSE;
+    }
+
+    /* APNs roughly follow the same rules as DNS domain names.  Allowed
+     * characters are a-z, 0-9, . and -.  GSM 03.03 Section 9.1 states:
+     *
+     *   The syntax of the APN shall follow the Name Syntax defined in
+     *   RFC 2181 [14] and RFC 1035 [15]. The APN consists of one or
+     *   more labels. Each label is coded as one octet length field
+     *   followed by that number of octets coded as 8 bit ASCII characters.
+     *   Following RFC 1035 [15] the labels should consist only of the
+     *   alphabetic characters (A-Z and a-z), digits (0-9) and the
+     *   dash (-). The case of alphabetic characters is not significant.
+     *
+     * A dot (.) is commonly used to separate parts of the APN, and
+     * apparently the underscore (_) is used as well.  RFC 2181 indicates
+     * that no restrictions of any kind are placed on DNS labels, and thus
+     * it would appear that none are placed on APNs either, but many modems
+     * and networks will fail to accept APNs that include odd characters
+     * like space ( ) and such.
+     */
+    for (i = 0; i < apn_len; i++) {
+        if (g_ascii_isalnum(apn[i]))
+            continue;
+        if (NM_IN_SET(apn[i], '.', '_', '-'))
+            continue;
+
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("'%s' contains invalid char(s) (use [A-Za-z._-])"),
+                    apn);
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, property_name);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
 static gboolean
 verify(NMSetting *setting, NMConnection *connection, GError **error)
 {
@@ -300,51 +401,11 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         return FALSE;
     }
 
-    if (priv->apn) {
-        gsize apn_len = strlen(priv->apn);
-        gsize i;
-
-        if (apn_len > 64) {
-            g_set_error(error,
-                        NM_CONNECTION_ERROR,
-                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                        _("property value '%s' is empty or too long (>64)"),
-                        priv->apn);
-            g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_APN);
-            return FALSE;
-        }
+    if (!_verify_apn(priv->apn, TRUE, NM_SETTING_GSM_APN, error))
+        return FALSE;
 
-        /* APNs roughly follow the same rules as DNS domain names.  Allowed
-         * characters are a-z, 0-9, . and -.  GSM 03.03 Section 9.1 states:
-         *
-         *   The syntax of the APN shall follow the Name Syntax defined in
-         *   RFC 2181 [14] and RFC 1035 [15]. The APN consists of one or
-         *   more labels. Each label is coded as one octet length field
-         *   followed by that number of octets coded as 8 bit ASCII characters.
-         *   Following RFC 1035 [15] the labels should consist only of the
-         *   alphabetic characters (A-Z and a-z), digits (0-9) and the
-         *   dash (-). The case of alphabetic characters is not significant.
-         *
-         * A dot (.) is commonly used to separate parts of the APN, and
-         * apparently the underscore (_) is used as well.  RFC 2181 indicates
-         * that no restrictions of any kind are placed on DNS labels, and thus
-         * it would appear that none are placed on APNs either, but many modems
-         * and networks will fail to accept APNs that include odd characters
-         * like space ( ) and such.
-         */
-        for (i = 0; i < apn_len; i++) {
-            if (!g_ascii_isalnum(priv->apn[i]) && (priv->apn[i] != '.') && (priv->apn[i] != '_')
-                && (priv->apn[i] != '-')) {
-                g_set_error(error,
-                            NM_CONNECTION_ERROR,
-                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                            _("'%s' contains invalid char(s) (use [A-Za-z._-])"),
-                            priv->apn);
-                g_prefix_error(error, "%s.%s: ", NM_SETTING_GSM_SETTING_NAME, NM_SETTING_GSM_APN);
-                return FALSE;
-            }
-        }
-    }
+    if (!_verify_apn(priv->initial_eps_apn, FALSE, NM_SETTING_GSM_INITIAL_EPS_BEARER_APN, error))
+        return FALSE;
 
     if (priv->username && priv->username[0] == '\0') {
         g_set_error_literal(error,
@@ -742,6 +803,41 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               NMSettingGsmPrivate,
                                               mtu);
 
+    /**
+     * NMSettingGsm:initial-eps-bearer-configure:
+     *
+     * For LTE modems, this setting determines whether the initial EPS bearer
+     * shall be configured when bringing up the connection.  It is inferred TRUE
+     * if initial-eps-bearer-apn is set.
+     *
+     * Since: 1.44
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_GSM_INITIAL_EPS_BEARER_CONFIGURE,
+                                               PROP_INITIAL_EPS_CONFIG,
+                                               FALSE,
+                                               NM_SETTING_PARAM_NONE,
+                                               NMSettingGsmPrivate,
+                                               initial_eps_config);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-apn:
+     *
+     * For LTE modems, this sets the APN for the initial EPS bearer that is set
+     * up when attaching to the network.  Setting this parameter implies
+     * initial-eps-bearer-configure to be TRUE.
+     *
+     * Since: 1.44
+     **/
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_GSM_INITIAL_EPS_BEARER_APN,
+                                              PROP_INITIAL_EPS_APN,
+                                              NM_SETTING_PARAM_NONE,
+                                              NMSettingGsmPrivate,
+                                              initial_eps_apn);
+
     /* Ignore incoming deprecated properties */
     _nm_properties_override_dbus(properties_override,
                                  "allowed-bands",
diff --git a/src/libnm-core-impl/nm-setting-infiniband.c b/src/libnm-core-impl/nm-setting-infiniband.c
index a3bda692..a0d5578c 100644
--- a/src/libnm-core-impl/nm-setting-infiniband.c
+++ b/src/libnm-core-impl/nm-setting-infiniband.c
@@ -36,9 +36,7 @@ typedef struct {
     char   *mac_address;
     char   *transport_mode;
     char   *parent;
-    char   *virtual_iface_name;
-    gsize   virtual_iface_name_parent_length;
-    gint32  virtual_iface_name_p_key;
+    char    virtual_iface_name[NM_IFNAMSIZ];
     gint32  p_key;
     guint32 mtu;
 } NMSettingInfinibandPrivate;
@@ -145,17 +143,6 @@ nm_setting_infiniband_get_parent(NMSettingInfiniband *setting)
     return NM_SETTING_INFINIBAND_GET_PRIVATE(setting)->parent;
 }
 
-char *
-nm_setting_infiniband_create_virtual_interface_name(const char *parent, int p_key)
-{
-    char *s;
-
-    s = g_strdup_printf("%s.%04x", parent, (guint) p_key);
-    if (strlen(s) >= NMP_IFNAMSIZ)
-        s[NMP_IFNAMSIZ - 1] = '\0';
-    return s;
-}
-
 /**
  * nm_setting_infiniband_get_virtual_interface_name:
  * @setting: the #NMSettingInfiniband
@@ -170,25 +157,11 @@ const char *
 nm_setting_infiniband_get_virtual_interface_name(NMSettingInfiniband *setting)
 {
     NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE(setting);
-    gsize                       len;
 
-    if (priv->p_key == -1 || !priv->parent) {
-        nm_clear_g_free(&priv->virtual_iface_name);
+    if (priv->p_key == -1 || !priv->parent)
         return NULL;
-    }
-
-    len = strlen(priv->parent);
-    if (!priv->virtual_iface_name || priv->virtual_iface_name_p_key != priv->p_key
-        || priv->virtual_iface_name_parent_length != len
-        || memcmp(priv->parent, priv->virtual_iface_name, len) != 0) {
-        priv->virtual_iface_name_p_key         = priv->p_key;
-        priv->virtual_iface_name_parent_length = len;
-        g_free(priv->virtual_iface_name);
-        priv->virtual_iface_name =
-            nm_setting_infiniband_create_virtual_interface_name(priv->parent, priv->p_key);
-    }
 
-    return priv->virtual_iface_name;
+    return nm_net_devname_infiniband(priv->virtual_iface_name, priv->parent, priv->p_key);
 }
 
 static gboolean
@@ -353,16 +326,6 @@ nm_setting_infiniband_new(void)
 }
 
 static void
-finalize(GObject *object)
-{
-    NMSettingInfinibandPrivate *priv = NM_SETTING_INFINIBAND_GET_PRIVATE(object);
-
-    g_free(priv->virtual_iface_name);
-
-    G_OBJECT_CLASS(nm_setting_infiniband_parent_class)->finalize(object);
-}
-
-static void
 nm_setting_infiniband_class_init(NMSettingInfinibandClass *klass)
 {
     GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
@@ -373,7 +336,6 @@ nm_setting_infiniband_class_init(NMSettingInfinibandClass *klass)
 
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
-    object_class->finalize     = finalize;
 
     setting_class->verify = verify;
 
diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c
index 3e49eb15..727a4bae 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -553,7 +553,7 @@ nm_ip_address_get_attribute(NMIPAddress *address, const char *name)
  * nm_ip_address_set_attribute:
  * @address: the #NMIPAddress
  * @name: the name of an address attribute
- * @value: (transfer none) (allow-none): the value
+ * @value: (transfer none) (nullable): the value
  *
  * Sets or clears the named attribute on @address to the given value.
  **/
@@ -602,7 +602,7 @@ struct NMIPRoute {
  *   <literal>AF_INET6</literal>)
  * @dest: the IP address of the route's destination
  * @prefix: the address prefix length
- * @next_hop: (allow-none): the IP address of the next hop (or %NULL)
+ * @next_hop: (nullable): the IP address of the next hop (or %NULL)
  * @metric: the route metric (or -1 for "default")
  * @error: location to store error, or %NULL
  *
@@ -653,7 +653,7 @@ nm_ip_route_new(int         family,
  *   <literal>AF_INET6</literal>)
  * @dest: the IP address of the route's destination
  * @prefix: the address prefix length
- * @next_hop: (allow-none): the IP address of the next hop (or %NULL)
+ * @next_hop: (nullable): the IP address of the next hop (or %NULL)
  * @metric: the route metric (or -1 for "default")
  * @error: location to store error, or %NULL
  *
@@ -995,7 +995,7 @@ nm_ip_route_get_next_hop(NMIPRoute *route)
 /**
  * nm_ip_route_set_next_hop:
  * @route: the #NMIPRoute
- * @next_hop: (allow-none): the route's next hop, as a string
+ * @next_hop: (nullable): the route's next hop, as a string
  *
  * Sets the next-hop property of this route object.
  *
@@ -1113,13 +1113,13 @@ _nm_ip_route_get_attributes(NMIPRoute *route)
  * @route: the #NMIPRoute
  * @sorted: whether to sort the names. Otherwise, their order is
  *   undefined and unstable.
- * @out_length: (allow-none) (out): the number of elements
+ * @out_length: (out) (optional): the number of elements
  *
  * Gets an array of attribute names defined on @route.
  *
- * Returns: (array length=out_length) (transfer container): a %NULL-terminated array
- *   of attribute names or %NULL if there are no attributes. The order of the returned
- *   names depends on @sorted.
+ * Returns: (array length=out_length) (transfer container) (nullable): a %NULL-terminated
+ *   array of attribute names or %NULL if there are no attributes. The order of the
+ *   returned names depends on @sorted.
  **/
 const char **
 _nm_ip_route_get_attribute_names(const NMIPRoute *route, gboolean sorted, guint *out_length)
@@ -1174,7 +1174,7 @@ nm_ip_route_get_attribute(NMIPRoute *route, const char *name)
  * nm_ip_route_set_attribute:
  * @route: the #NMIPRoute
  * @name: the name of a route attribute
- * @value: (transfer none) (allow-none): the value
+ * @value: (transfer none) (nullable): the value
  *
  * Sets the named attribute on @route to the given value.
  **/
@@ -1461,7 +1461,7 @@ _ip_route_attribute_validate(const char           *name,
  * @value: the attribute value
  * @family: IP address family of the route
  * @known: (out): on return, whether the attribute name is a known one
- * @error: (allow-none): return location for a #GError, or %NULL
+ * @error: return location for a #GError, or %NULL
  *
  * Validates a route attribute, i.e. checks that the attribute is a known one
  * and the value is of the correct type and well-formed.
@@ -1734,7 +1734,7 @@ nm_ip_routing_rule_new_clone(const NMIPRoutingRule *rule)
 
 /**
  * nm_ip_routing_rule_ref:
- * @self: (allow-none): the #NMIPRoutingRule instance
+ * @self: (nullable): the #NMIPRoutingRule instance
  *
  * Increases the reference count of the instance.
  *
@@ -1761,7 +1761,7 @@ nm_ip_routing_rule_ref(NMIPRoutingRule *self)
 
 /**
  * nm_ip_routing_rule_unref:
- * @self: (allow-none): the #NMIPRoutingRule instance
+ * @self: (nullable): the #NMIPRoutingRule instance
  *
  * Decreases the reference count of the instance and destroys
  * the instance if the reference count reaches zero.
@@ -1984,7 +1984,7 @@ nm_ip_routing_rule_set_from_bin(NMIPRoutingRule *self, gconstpointer from, guint
 /**
  * nm_ip_routing_rule_set_from:
  * @self: the #NMIPRoutingRule instance
- * @from: (allow-none): the from/src address to set.
+ * @from: (nullable): the from/src address to set.
  *   The address family must match.
  * @len: the corresponding prefix length of the address.
  *
@@ -2084,7 +2084,7 @@ nm_ip_routing_rule_set_to_bin(NMIPRoutingRule *self, gconstpointer to, guint8 le
 /**
  * nm_ip_routing_rule_set_to:
  * @self: the #NMIPRoutingRule instance
- * @to: (allow-none): the to/dst address to set.
+ * @to: (nullable): the to/dst address to set.
  *   The address family must match.
  * @len: the corresponding prefix length of the address.
  *   If @to is %NULL, this valid is ignored.
@@ -2371,7 +2371,7 @@ nm_ip_routing_rule_get_xifname_bin(const NMIPRoutingRule *self,
 /**
  * nm_ip_routing_rule_set_iifname:
  * @self: the #NMIPRoutingRule instance.
- * @iifname: (allow-none): the iifname to set or %NULL to unset.
+ * @iifname: (nullable): the iifname to set or %NULL to unset.
  *
  * The name supports C backslash escaping for non-UTF-8 characters.
  * Note that nm_ip_routing_rule_from_string() too uses backslash
@@ -2408,7 +2408,7 @@ nm_ip_routing_rule_get_oifname(const NMIPRoutingRule *self)
 /**
  * nm_ip_routing_rule_set_oifname:
  * @self: the #NMIPRoutingRule instance.
- * @oifname: (allow-none): the oifname to set or %NULL to unset.
+ * @oifname: (nullable): the oifname to set or %NULL to unset.
  *
  * The name supports C backslash escaping for non-UTF-8 characters.
  * Note that nm_ip_routing_rule_from_string() too uses backslash
@@ -2526,9 +2526,9 @@ nm_ip_routing_rule_set_suppress_prefixlength(NMIPRoutingRule *self, gint32 suppr
 /**
  * nm_ip_routing_rule_get_uid_range:
  * @self: the #NMIPRoutingRule instance
- * @out_range_start: (out) (allow-none): returns the start of the range
+ * @out_range_start: (out) (optional): returns the start of the range
  *   or 0 if the range is not set.
- * @out_range_end: (out) (allow-none): returns the end of the range
+ * @out_range_end: (out) (optional): returns the end of the range
  *   or 0 if the range is not set.
  *
  * Returns: %TRUE if a uid range is set.
@@ -2586,8 +2586,8 @@ nm_ip_routing_rule_set_uid_range(NMIPRoutingRule *self,
 
 /**
  * nm_ip_routing_rule_cmp:
- * @rule: (allow-none): the #NMIPRoutingRule instance to compare
- * @other: (allow-none): the other #NMIPRoutingRule instance to compare
+ * @rule: (nullable): the #NMIPRoutingRule instance to compare
+ * @other: (nullable): the other #NMIPRoutingRule instance to compare
  *
  * Returns: zero, a positive, or a negative integer to indicate
  *   equality or how the arguments compare.
@@ -2709,7 +2709,7 @@ _rr_xport_range_parse(char *str, gint64 *out_start, guint16 *out_end)
 /**
  * nm_ip_routing_rule_validate:
  * @self: the #NMIPRoutingRule instance to validate
- * @error: (allow-none) (out): the error result if validation fails.
+ * @error: the error result if validation fails.
  *
  * Returns: %TRUE if the rule validates.
  *
@@ -3349,9 +3349,9 @@ _rr_string_addr_family_from_flags(NMIPRoutingRuleAsStringFlags to_string_flags)
  * @str: the string representation to convert to an #NMIPRoutingRule
  * @to_string_flags: #NMIPRoutingRuleAsStringFlags for controlling the
  *   string conversion.
- * @extra_args: (allow-none): extra arguments for controlling the string
+ * @extra_args: (nullable): extra arguments for controlling the string
  *   conversion. Currently, not extra arguments are supported.
- * @error: (allow-none) (out): the error reason.
+ * @error: the error reason.
  *
  * Returns: (transfer full): the new #NMIPRoutingRule or %NULL on error.
  *
@@ -3813,9 +3813,9 @@ _rr_string_append_inet_addr(NMStrBuf       *str,
  * @self: the #NMIPRoutingRule instance to convert to string.
  * @to_string_flags: #NMIPRoutingRuleAsStringFlags for controlling the
  *   string conversion.
- * @extra_args: (allow-none): extra arguments for controlling the string
+ * @extra_args: (nullable): extra arguments for controlling the string
  *   conversion. Currently, not extra arguments are supported.
- * @error: (allow-none) (out): the error reason.
+ * @error: the error reason.
  *
  * Returns: (transfer full): the string representation or %NULL on error.
  *
@@ -5349,7 +5349,7 @@ nm_setting_ip_config_get_dhcp_iaid(NMSettingIPConfig *setting)
 /**
  * nm_setting_ip_config_get_dhcp_reject_servers:
  * @setting: the #NMSettingIPConfig
- * @out_len: (allow-none) (out): the number of returned elements
+ * @out_len: (out) (optional): the number of returned elements
  *
  * Returns: (array length=out_len zero-terminated=1) (transfer none):
  *   A %NULL terminated array of DHCP reject servers. Even if no reject
@@ -5453,7 +5453,7 @@ nm_setting_ip_config_get_auto_route_ext_gw(NMSettingIPConfig *setting)
  *
  * Returns: the #NMSettingIPConfig:replace-local-rule property of the setting
  *
- * Since: 1.44, 1.42.2
+ * Since: 1.44
  **/
 NMTernary
 nm_setting_ip_config_get_replace_local_rule(NMSettingIPConfig *setting)
@@ -6730,7 +6730,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      *
      * A string containing the "Identity Association Identifier" (IAID) used by
      * the DHCP client. The string can be a 32-bit number (either decimal,
-     * hexadecimal or or as colon separated hexadecimal numbers). Alternatively
+     * hexadecimal or as colon separated hexadecimal numbers). Alternatively
      * it can be set to the special values "mac", "perm-mac", "ifname" or
      * "stable". When set to "mac" (or "perm-mac"), the last 4 bytes of the
      * current (or permanent) MAC address are used as IAID. When set to
@@ -6839,7 +6839,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      * Connections will default to keep the autogenerated priority 0 local rule
      * unless this setting is set to %TRUE.
      *
-     * Since: 1.44, 1.42.2
+     * Since: 1.44
      */
     obj_properties[PROP_REPLACE_LOCAL_RULE] =
         g_param_spec_enum(NM_SETTING_IP_CONFIG_REPLACE_LOCAL_RULE,
diff --git a/src/libnm-core-impl/nm-setting-ip4-config.c b/src/libnm-core-impl/nm-setting-ip4-config.c
index 7819c2e8..9ed39507 100644
--- a/src/libnm-core-impl/nm-setting-ip4-config.c
+++ b/src/libnm-core-impl/nm-setting-ip4-config.c
@@ -778,6 +778,34 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      * ---end---
      */
 
+    /* ---nmcli---
+     * property: dns-options
+     * format: a comma separated list of DNS options
+     * description:
+     *   DNS options for /etc/resolv.conf as described in resolv.conf(5) manual.
+     *
+     *   The currently supported options are "attempts", "debug", "edns0",
+     *   "ndots", "no-aaaa", "no-check-names", "no-reload", "no-tld-query",
+     *   "rotate", "single-request", "single-request-reopen", "timeout",
+     *   "trust-ad", "use-vc". See the resolv.conf(5) manual.
+     *
+     *   Note that there is a distinction between an unset (default) list
+     *   and an empty list. In nmcli, to unset the list set the value to
+     *   "". To set an empty list, set it to " ". Currently, an unset list
+     *   has the same meaning as an empty list. That might change in the future.
+     *
+     *   The "trust-ad" setting is only honored if the profile contributes
+     *   name servers to resolv.conf, and if all contributing profiles have
+     *   "trust-ad" enabled.
+     *
+     *   When using a caching DNS plugin (dnsmasq or systemd-resolved in
+     *   NetworkManager.conf) then "edns0" and "trust-ad" are automatically
+     *   added.
+     *
+     *   The valid "ipv4.dns-options" and "ipv6.dns-options" get merged together.
+     * ---end---
+     */
+
     /* ---ifcfg-rh---
      * property: dns-options
      * variable: RES_OPTIONS(+)
@@ -1224,6 +1252,36 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      * ---end---
      */
 
+    /* ---nmcli---
+     * property: method
+     * format: string
+     * description: The IPv4 connection method.
+     * description-docbook:
+     *   <para>
+     *     Sets the IPv4 connection method. You can set one of the following values:
+     *   </para>
+     *   <para>
+     *     <itemizedlist>
+     *        <listitem>
+     *          <para><literal>"auto"</literal> - Enables automatic IPv4 address assignment from DHCP, PPP, or similar services.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"manual"</literal> - Enables the configuration of static IPv4 addresses on the interface. Note that you must set at least one IP address and subnet mask in the "ipv4.addresses" property.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"disabled"</literal> - Disables the IPv4 protocol in this connection profile.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"shared"</literal> - Provides network access to other computers. If you do not specify an IP address and subnet mask in "ipv4.addresses", NetworkManager assigns 10.42.x.1/24 to the interface. Additionally, NetworkManager starts a DHCP server and DNS forwarder. Hosts that connect to this interface will then receive an IP address from the configured range, and NetworkManager configures NAT to map client addresses to the one of the current default network connection.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"link-local"</literal> - Enables link-local addresses according to RFC 3927. NetworkManager assigns a random link-local address from the 169.254.0.0/16 subnet to the interface.</para>
+     *        </listitem>
+     *     </itemizedlist>
+     *   </para>
+     * ---end---
+     */
+
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
     _nm_setting_class_commit(setting_class,
diff --git a/src/libnm-core-impl/nm-setting-ip6-config.c b/src/libnm-core-impl/nm-setting-ip6-config.c
index 573211b2..661a451e 100644
--- a/src/libnm-core-impl/nm-setting-ip6-config.c
+++ b/src/libnm-core-impl/nm-setting-ip6-config.c
@@ -44,13 +44,15 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_IP6_PRIVACY,
                                   PROP_TOKEN,
                                   PROP_DHCP_DUID,
                                   PROP_RA_TIMEOUT,
-                                  PROP_MTU, );
+                                  PROP_MTU,
+                                  PROP_DHCP_PD_HINT, );
 
 typedef struct {
     NMSettingIPConfigPrivate parent;
 
     char   *token;
     char   *dhcp_duid;
+    char   *dhcp_pd_hint;
     int     ip6_privacy;
     gint32  addr_gen_mode;
     gint32  ra_timeout;
@@ -98,6 +100,26 @@ nm_setting_ip6_config_get_ip6_privacy(NMSettingIP6Config *setting)
 }
 
 /**
+ * nm_setting_ip6_config_get_dhcp_pd_hint:
+ * @setting: the #NMSettingIP6Config
+ *
+ * Returns the value contained in the #NMSettingIP6Config:dhcp-pd-hint
+ * property.
+ *
+ * Returns: a string containing an address and prefix length to be used
+ * as hint for DHCPv6 prefix delegation.
+ *
+ * Since: 1.44
+ **/
+const char *
+nm_setting_ip6_config_get_dhcp_pd_hint(NMSettingIP6Config *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IP6_CONFIG(setting), NULL);
+
+    return NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting)->dhcp_pd_hint;
+}
+
+/**
  * nm_setting_ip6_config_get_addr_gen_mode:
  * @setting: the #NMSettingIP6Config
  *
@@ -349,6 +371,23 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
+    if (priv->dhcp_pd_hint) {
+        int prefix;
+
+        if (!nm_inet_parse_with_prefix_bin(AF_INET6, priv->dhcp_pd_hint, NULL, NULL, &prefix)
+            || prefix < 1 || prefix > 128) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("must be a valid IPv6 address with prefix"));
+            g_prefix_error(error,
+                           "%s.%s: ",
+                           NM_SETTING_IP6_CONFIG_SETTING_NAME,
+                           NM_SETTING_IP6_CONFIG_DHCP_PD_HINT);
+            return FALSE;
+        }
+    }
+
     /* Failures from here on, are NORMALIZABLE_ERROR... */
 
     if (token_needs_normalization) {
@@ -523,6 +562,41 @@ ip6_route_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
     return TRUE;
 }
 
+static gboolean
+_set_string_fcn_dhcp_pd_hint(const NMSettInfoSetting  *sett_info,
+                             const NMSettInfoProperty *property_info,
+                             NMSetting                *setting,
+                             const char               *str)
+{
+    NMSettingIP6ConfigPrivate *priv = NM_SETTING_IP6_CONFIG_GET_PRIVATE(setting);
+    char                       buf[NM_INET_ADDRSTRLEN];
+    char                       bufp[NM_INET_ADDRSTRLEN + 16];
+    gs_free char              *old = NULL;
+    NMIPAddr                   addr;
+    int                        prefix;
+
+    if (!str)
+        goto do_set;
+
+    if (!nm_inet_parse_with_prefix_bin(AF_INET6, str, NULL, &addr, &prefix)) {
+        /* address not valid, set as is */
+        goto do_set;
+    }
+
+    /* address valid, normalize */
+    nm_inet6_ntop(&addr.addr6, buf);
+    nm_sprintf_buf(bufp, "%s/%d", buf, prefix);
+    str = bufp;
+
+do_set:
+    if (!nm_streq0(priv->dhcp_pd_hint, str)) {
+        old                = priv->dhcp_pd_hint;
+        priv->dhcp_pd_hint = g_strdup(str);
+        return TRUE;
+    }
+    return FALSE;
+}
+
 /*****************************************************************************/
 
 static void
@@ -733,6 +807,35 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      * ---end---
      */
 
+    /* ---nmcli---
+     * property: dns-options
+     * format: a comma separated list of DNS options
+     * description:
+     *   DNS options for /etc/resolv.conf as described in resolv.conf(5) manual.
+     *
+     *   The currently supported options are "attempts", "debug", "edns0",
+     *   "ndots", "no-aaaa", "no-check-names", "no-reload", "no-tld-query",
+     *   "rotate", "single-request", "single-request-reopen", "timeout",
+     *   "trust-ad", "use-vc" and "inet6", "ip6-bytestring", "ip6-dotint",
+     *   "no-ip6-dotint". See the resolv.conf(5) manual.
+     *
+     *   Note that there is a distinction between an unset (default) list
+     *   and an empty list. In nmcli, to unset the list set the value to
+     *   "". To set an empty list, set it to " ". Currently, an unset list
+     *   has the same meaning as an empty list. That might change in the future.
+     *
+     *   The "trust-ad" setting is only honored if the profile contributes
+     *   name servers to resolv.conf, and if all contributing profiles have
+     *   "trust-ad" enabled.
+     *
+     *   When using a caching DNS plugin (dnsmasq or systemd-resolved in
+     *   NetworkManager.conf) then "edns0" and "trust-ad" are automatically
+     *   added.
+     *
+     *   The valid "ipv4.dns-options" and "ipv6.dns-options" get merged together.
+     * ---end---
+     */
+
     /* ---ifcfg-rh---
      * property: dns-options
      * variable: IPV6_RES_OPTIONS(+)
@@ -805,37 +908,42 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
     /**
      * NMSettingIP6Config:addr-gen-mode:
      *
-     * Configure method for creating the address for use with RFC4862 IPv6
-     * Stateless Address Autoconfiguration. The permitted values are:
-     * %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+     * Configure the method for creating the IPv6 interface identifier of
+     * addresses for RFC4862 IPv6 Stateless Address Autoconfiguration and IPv6
+     * Link Local.
+     *
+     * The permitted values are: %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
      * %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY.
-     * %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64
-     * or %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT.
+     * %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64 or
+     * %NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT.
      *
-     * If the property is set to EUI64, the addresses will be generated
-     * using the interface tokens derived from hardware address. This makes
-     * the host part of the address to stay constant, making it possible
-     * to track host's presence when it changes networks. The address changes
-     * when the interface hardware is replaced.
+     * If the property is set to "eui64", the addresses will be generated using
+     * the interface token derived from the hardware address. This makes the
+     * host part of the address constant, making it possible to track the
+     * host's presence when it changes networks. The address changes when the
+     * interface hardware is replaced. If a duplicate address is detected,
+     * there is no fallback to generate another address. When configured, the
+     * "ipv6.token" is used instead of the MAC address to generate addresses
+     * for stateless autoconfiguration.
      *
-     * The value of stable-privacy enables use of cryptographically
-     * secure hash of a secret host-specific key along with the connection's
-     * stable-id and the network address as specified by RFC7217.
-     * This makes it impossible to use the address track host's presence,
-     * and makes the address stable when the network interface hardware is
-     * replaced.
+     * If the property is set to "stable-privacy", the interface identifier is
+     * generated as specified by RFC7217. This works by hashing a host specific
+     * key (see NetworkManager(8) manual), the interface name, the connection's
+     * "connection.stable-id" property and the address prefix.  This improves
+     * privacy by making it harder to use the address to track the host's
+     * presence as every prefix and network has a different identifier. Also,
+     * the address is stable when the network interface hardware is replaced.
      *
-     * The special values "default" and "default-or-eui64" will fallback to the global
-     * connection default in as documented in NetworkManager.conf(5) manual. If the
-     * global default is not specified, the fallback value is "stable-privacy"
-     * or "eui64", respectively.
+     * The special values "default" and "default-or-eui64" will fallback to the
+     * global connection default as documented in the NetworkManager.conf(5)
+     * manual. If the global default is not specified, the fallback value is
+     * "stable-privacy" or "eui64", respectively.
      *
-     * For libnm, the property defaults to "default" since 1.40.
-     * Previously it defaulted to "stable-privacy".
-     * On D-Bus, the absence of an addr-gen-mode setting equals
-     * "default". For keyfile plugin, the absence of the setting
-     * on disk means "default-or-eui64" so that the property doesn't change on upgrade
-     * from older versions.
+     * For libnm, the property defaults to "default" since 1.40.  Previously it
+     * used to default to "stable-privacy".  On D-Bus, the absence of an
+     * addr-gen-mode setting equals "default". For keyfile plugin, the absence
+     * of the setting on disk means "default-or-eui64" so that the property
+     * doesn't change on upgrade from older versions.
      *
      * Note that this setting is distinct from the Privacy Extensions as
      * configured by "ip6-privacy" property and it does not affect the
@@ -852,6 +960,45 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      * example: IPV6_ADDR_GEN_MODE=stable-privacy
      * ---end---
      */
+    /* ---nmcli---
+     * property: addr-gen-mode
+     * format: one of "eui64" (0), "stable-privacy" (1), "default" (3) or "default-or-eui64" (2)
+     * description: Configure method for creating the
+     * IPv6 interface identifer of addresses with RFC4862 IPv6 Stateless
+     * Address Autoconfiguration and Link Local addresses.
+     *
+     * The permitted values are: "eui64" (0), "stable-privacy" (1), "default"
+     * (3) or "default-or-eui64" (2).
+     *
+     * If the property is set to "eui64", the addresses will be generated using
+     * the interface token derived from hardware address. This makes the host
+     * part of the address to stay constant, making it possible to track the
+     * host's presence when it changes networks. The address changes when the
+     * interface hardware is replaced. If a duplicate address is detected,
+     * there is also no fallback to generate another address. When configured,
+     * the "ipv6.token" is used instead of the MAC address to generate
+     * addresses for stateless autoconfiguration.
+     *
+     * If the property is set to "stable-privacy", the interface identifier is
+     * generated as specified by RFC7217. This works by hashing a host specific
+     * key (see NetworkManager(8) manual), the interface name, the connection's
+     * "connection.stable-id" property and the address prefix.  This improves
+     * privacy by making it harder to use the address to track the host's
+     * presence and the address is stable when the network interface hardware
+     * is replaced.
+     *
+     * The special values "default" and "default-or-eui64" will fallback to the
+     * global connection default as documented in the NetworkManager.conf(5)
+     * manual. If the global default is not specified, the fallback value is
+     * "stable-privacy" or "eui64", respectively.
+     *
+     * If not specified, when creating a new profile the default is "default".
+     *
+     * Note that this setting is distinct from the Privacy Extensions as
+     * configured by "ip6-privacy" property and it does not affect the
+     * temporary addresses configured with this option.
+     * ---end---
+     */
     _nm_setting_property_define_direct_int32(properties_override,
                                              obj_properties,
                                              NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
@@ -869,6 +1016,10 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      * Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02
      * IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode.
      *
+     * When set, the token is used as IPv6 interface identifier instead of the
+     * hardware address. This only applies to addresses from stateless
+     * autoconfiguration, not to IPv6 link local addresses.
+     *
      * Since: 1.4
      **/
     /* ---ifcfg-rh---
@@ -989,6 +1140,35 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
                                               NMSettingIP6ConfigPrivate,
                                               dhcp_duid);
 
+    /**
+     * NMSettingIP6Config:dhcp-pd-hint:
+     *
+     * A IPv6 address followed by a slash and a prefix length. If set, the value is
+     * sent to the DHCPv6 server as hint indicating the prefix delegation (IA_PD) we
+     * want to receive.
+     * To only hint a prefix length without prefix, set the address part to the
+     * zero address (for example "::/60").
+     *
+     * Since: 1.44
+     **/
+    /* ---ifcfg-rh---
+     * property: dhcp-pd-hint
+     * variable: DHCPV6_PD_HINT(+)
+     * description: Hint for DHCPv6 prefix delegation
+     * example: DHCPV6_PD_HINT=2001:db8:1111:2220::/60
+     *          DHCPV6_PD_HINT=::/60
+     * ---end---
+     */
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_IP6_CONFIG_DHCP_PD_HINT,
+                                              PROP_DHCP_PD_HINT,
+                                              NM_SETTING_PARAM_NONE,
+                                              NMSettingIP6ConfigPrivate,
+                                              dhcp_pd_hint,
+                                              .direct_hook.set_string_fcn =
+                                                  _set_string_fcn_dhcp_pd_hint);
+
     /* IP6-specific property overrides */
 
     /* ---dbus---
@@ -1075,6 +1255,7 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
     /* ---nmcli---
      * property: routes
      * format: a comma separated list of routes
+     * description: Array of IP routes.
      * description-docbook:
      *   <para>
      *     A list of IPv6 destination addresses, prefix length, optional IPv6
@@ -1199,6 +1380,45 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      * ---end---
      */
 
+    /* ---nmcli---
+     * property: method
+     * format: string
+     * description: The IPv6 connection method.
+     * description-docbook:
+     *   <para>
+     *     Sets the IPv6 connection method. You can set one of the following values:
+     *   </para>
+     *   <para>
+     *     <itemizedlist>
+     *        <listitem>
+     *          <para><literal>"auto"</literal> - Enables IPv6 auto-configuration. By default, NetworkManager uses Router Advertisements and, if the router announces the "managed" flag, NetworkManager requests an IPv6 address and prefix from a DHCPv6 server.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"dhcp"</literal> - Requests an IPv6 address and prefix from a DHCPv6 server. Note that DHCPv6 does not have options to provide routes and the default gateway. As a consequence, by using the "dhcp" method, connections are limited to their own subnet.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"manual"</literal> - Enables the configuration of static IPv6 addresses on the interface. Note that you must set at least one IP address and prefix in the "ipv6.addresses" property.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"disabled"</literal> - Disables the IPv6 protocol in this connection profile.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"ignore"</literal> - Configures NetworkManager to make no changes to the IPv6 configuration on the interface. For example, you can then use the "accept_ra" feature of the kernel to accept Router Advertisements.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"shared"</literal> - Provides network access to other computers. NetworkManager requests a prefix from an upstream DHCPv6 server, assigns an address to the interface, and announces the prefix to clients that connect to this interface.</para>
+     *        </listitem>
+     *        <listitem>
+     *          <para><literal>"link-local"</literal> - Assigns a random link-local address from the fe80::/64 subnet to the interface.</para>
+     *        </listitem>
+     *     </itemizedlist>
+     *     <para>
+     *       If you set <literal>"auto"</literal>, <literal>"dhcp"</literal>, <literal>"manual"</literal>, <literal>"ignore"</literal>, or <literal>"shared"</literal>, NetworkManager assigns, in addition to the global address, an IPv6 link-local address to the interface. This is compliant with RFC 4291.
+     *     </para>
+     *   </para>
+     * ---end---
+     */
+
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
     _nm_setting_class_commit(setting_class,
diff --git a/src/libnm-core-impl/nm-setting-link.c b/src/libnm-core-impl/nm-setting-link.c
new file mode 100644
index 00000000..29d56d67
--- /dev/null
+++ b/src/libnm-core-impl/nm-setting-link.c
@@ -0,0 +1,241 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ */
+
+#include "libnm-core-impl/nm-default-libnm-core.h"
+
+#include "nm-setting-link.h"
+
+#include "nm-setting-private.h"
+#include "nm-utils-private.h"
+
+/**
+ * SECTION:nm-setting-link
+ * @short_description: Contains properties related to the link
+ * @include: nm-setting-link.h
+ **/
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettingLink,
+                             PROP_TX_QUEUE_LENGTH,
+                             PROP_GSO_MAX_SIZE,
+                             PROP_GSO_MAX_SEGMENTS,
+                             PROP_GRO_MAX_SIZE, );
+
+/**
+ * NMSettingLink:
+ *
+ * Link settings
+ *
+ * Since: 1.44
+ */
+struct _NMSettingLink {
+    NMSetting parent;
+    gint64    tx_queue_length;
+    gint64    gso_max_size;
+    gint64    gso_max_segments;
+    gint64    gro_max_size;
+};
+
+struct _NMSettingLinkClass {
+    NMSettingClass parent;
+};
+
+G_DEFINE_TYPE(NMSettingLink, nm_setting_link, NM_TYPE_SETTING)
+
+/**
+ * nm_setting_link_get_tx_queue_length:
+ * @setting: the #NMSettingLink
+ *
+ * Returns the value contained in the #NMSettingLink:tx-queue-length
+ * property.
+ *
+ * Returns: the 'tx-queue-length' property value
+ *
+ * Since: 1.44
+ **/
+gint64
+nm_setting_link_get_tx_queue_length(NMSettingLink *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_LINK(setting), 0);
+
+    return setting->tx_queue_length;
+}
+
+/**
+ * nm_setting_link_get_gso_max_size:
+ * @setting: the #NMSettingLink
+ *
+ * Returns the value contained in the #NMSettingLink:gso-max-size
+ * property.
+ *
+ * Returns: the 'gso-max-size' property value
+ *
+ * Since: 1.44
+ **/
+gint64
+nm_setting_link_get_gso_max_size(NMSettingLink *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_LINK(setting), 0);
+
+    return setting->gso_max_size;
+}
+
+/**
+ * nm_setting_link_get_gso_max_segments:
+ * @setting: the #NMSettingLink
+ *
+ * Returns the value contained in the #NMSettingLink:gso-max-segments
+ * property.
+ *
+ * Returns: the 'gso-max-segments' property value
+ *
+ * Since: 1.44
+ **/
+gint64
+nm_setting_link_get_gso_max_segments(NMSettingLink *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_LINK(setting), 0);
+
+    return setting->gso_max_segments;
+}
+
+/**
+ * nm_setting_link_get_gro_max_size:
+ * @setting: the #NMSettingLink
+ *
+ * Returns the value contained in the #NMSettingLink:gro-max-size
+ * property.
+ *
+ * Returns: the 'gro-max-size' property value
+ *
+ * Since: 1.44
+ **/
+gint64
+nm_setting_link_get_gro_max_size(NMSettingLink *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_LINK(setting), 0);
+
+    return setting->gro_max_size;
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_link_init(NMSettingLink *setting)
+{}
+
+/**
+ * nm_setting_link_new:
+ *
+ * Creates a new #NMSettingLink object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingLink object
+ *
+ * Since: 1.44
+ **/
+NMSetting *
+nm_setting_link_new(void)
+{
+    return g_object_new(NM_TYPE_SETTING_LINK, NULL);
+}
+
+static void
+nm_setting_link_class_init(NMSettingLinkClass *klass)
+{
+    GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
+    NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
+    GArray         *properties_override = _nm_sett_info_property_override_create_array();
+
+    object_class->get_property = _nm_setting_property_get_property_direct;
+    object_class->set_property = _nm_setting_property_set_property_direct;
+
+    /**
+     * NMSettingLink:tx-queue-length
+     *
+     * The size of the transmit queue for the device, in number of packets. The value
+     * must be between 0 and 4294967295. When set to -1, the existing value is preserved.
+     *
+     * Since: 1.44
+     **/
+    _nm_setting_property_define_direct_int64(properties_override,
+                                             obj_properties,
+                                             NM_SETTING_LINK_TX_QUEUE_LENGTH,
+                                             PROP_TX_QUEUE_LENGTH,
+                                             -1,
+                                             G_MAXUINT32,
+                                             -1,
+                                             NM_SETTING_PARAM_NONE,
+                                             NMSettingLink,
+                                             tx_queue_length);
+
+    /**
+     * NMSettingLink:gso-max-size
+     *
+     * The maximum size of a Generic Segment Offload packet the device should accept.
+     * The value must be between 0 and 4294967295. When set to -1, the existing value
+     * is preserved.
+     *
+     * Since: 1.44
+     **/
+    _nm_setting_property_define_direct_int64(properties_override,
+                                             obj_properties,
+                                             NM_SETTING_LINK_GSO_MAX_SIZE,
+                                             PROP_GSO_MAX_SIZE,
+                                             -1,
+                                             G_MAXUINT32,
+                                             -1,
+                                             NM_SETTING_PARAM_NONE,
+                                             NMSettingLink,
+                                             gso_max_size);
+
+    /**
+     * NMSettingLink:gso-max-segments
+     *
+     * The maximum segments of a Generic Segment Offload packet the device should accept.
+     * The value must be between 0 and 4294967295. When set to -1, the existing value
+     * is preserved.
+     *
+     * Since: 1.44
+     **/
+    _nm_setting_property_define_direct_int64(properties_override,
+                                             obj_properties,
+                                             NM_SETTING_LINK_GSO_MAX_SEGMENTS,
+                                             PROP_GSO_MAX_SEGMENTS,
+                                             -1,
+                                             G_MAXUINT32,
+                                             -1,
+                                             NM_SETTING_PARAM_NONE,
+                                             NMSettingLink,
+                                             gso_max_segments);
+
+    /**
+     * NMSettingLink:gro-max-size
+     *
+     * The maximum size of a packet built by the Generic Receive Offload stack for
+     * this device. The value must be between 0 and 4294967295. When set to -1, the
+     * existing value is preserved.
+     *
+     * Since: 1.44
+     **/
+    _nm_setting_property_define_direct_int64(properties_override,
+                                             obj_properties,
+                                             NM_SETTING_LINK_GRO_MAX_SIZE,
+                                             PROP_GRO_MAX_SIZE,
+                                             -1,
+                                             G_MAXUINT32,
+                                             -1,
+                                             NM_SETTING_PARAM_NONE,
+                                             NMSettingLink,
+                                             gro_max_size);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+    _nm_setting_class_commit(setting_class,
+                             NM_META_SETTING_TYPE_LINK,
+                             NULL,
+                             properties_override,
+                             0);
+}
diff --git a/src/libnm-core-impl/nm-setting-match.c b/src/libnm-core-impl/nm-setting-match.c
index f0e9fdf8..0ece65f1 100644
--- a/src/libnm-core-impl/nm-setting-match.c
+++ b/src/libnm-core-impl/nm-setting-match.c
@@ -171,7 +171,7 @@ nm_setting_match_clear_interface_names(NMSettingMatch *setting)
 /**
  * nm_setting_match_get_interface_names:
  * @setting: the #NMSettingMatch
- * @length: (out) (allow-none): the length of the returned interface names array.
+ * @length: (out) (optional): the length of the returned interface names array.
  *
  * Returns all the interface names.
  *
@@ -317,7 +317,7 @@ nm_setting_match_clear_kernel_command_lines(NMSettingMatch *setting)
 /**
  * nm_setting_match_get_kernel_command_lines:
  * @setting: the #NMSettingMatch
- * @length: (out) (allow-none): the length of the returned interface names array.
+ * @length: (out) (optional): the length of the returned interface names array.
  *
  * Returns all the interface names.
  *
@@ -456,7 +456,7 @@ nm_setting_match_clear_drivers(NMSettingMatch *setting)
 /**
  * nm_setting_match_get_drivers:
  * @setting: the #NMSettingMatch
- * @length: (out) (allow-none): the length of the returned interface names array.
+ * @length: (out) (optional): the length of the returned interface names array.
  *
  * Returns all the drivers.
  *
@@ -595,7 +595,7 @@ nm_setting_match_clear_paths(NMSettingMatch *setting)
 /**
  * nm_setting_match_get_paths:
  * @setting: the #NMSettingMatch
- * @length: (out) (allow-none): the length of the returned paths array.
+ * @length: (out) (optional): the length of the returned paths array.
  *
  * Returns all the paths.
  *
diff --git a/src/libnm-core-impl/nm-setting-ovs-external-ids.c b/src/libnm-core-impl/nm-setting-ovs-external-ids.c
index 54684380..0cdf3e6b 100644
--- a/src/libnm-core-impl/nm-setting-ovs-external-ids.c
+++ b/src/libnm-core-impl/nm-setting-ovs-external-ids.c
@@ -56,7 +56,7 @@ G_DEFINE_TYPE(NMSettingOvsExternalIDs, nm_setting_ovs_external_ids, NM_TYPE_SETT
 
 /**
  * nm_setting_ovs_external_ids_check_key:
- * @key: (allow-none): the key to check
+ * @key: (nullable): the key to check
  * @error: a #GError, %NULL to ignore.
  *
  * Checks whether @key is a valid key for OVS' external-ids.
@@ -171,7 +171,7 @@ _nm_setting_ovs_verify_connection_type(GType gtype, NMConnection *connection, GE
 
 /**
  * nm_setting_ovs_external_ids_check_val:
- * @val: (allow-none): the value to check
+ * @val: (nullable): the value to check
  * @error: a #GError, %NULL to ignore.
  *
  * Checks whether @val is a valid user data value. This means,
@@ -297,7 +297,7 @@ nm_setting_ovs_external_ids_get_data(NMSettingOvsExternalIDs *setting, const cha
  * nm_setting_ovs_external_ids_set_data:
  * @setting: the #NMSettingOvsExternalIDs instance
  * @key: the key to set
- * @val: (allow-none): the value to set or %NULL to clear a key.
+ * @val: (nullable): the value to set or %NULL to clear a key.
  *
  * Since: 1.30
  */
diff --git a/src/libnm-core-impl/nm-setting-ovs-other-config.c b/src/libnm-core-impl/nm-setting-ovs-other-config.c
index ca46ae95..46d991d4 100644
--- a/src/libnm-core-impl/nm-setting-ovs-other-config.c
+++ b/src/libnm-core-impl/nm-setting-ovs-other-config.c
@@ -135,7 +135,7 @@ nm_setting_ovs_other_config_get_data(NMSettingOvsOtherConfig *setting, const cha
  * nm_setting_ovs_other_config_set_data:
  * @setting: the #NMSettingOvsOtherConfig instance
  * @key: the key to set
- * @val: (allow-none): the value to set or %NULL to clear a key.
+ * @val: (nullable): the value to set or %NULL to clear a key.
  *
  * Since: 1.42
  */
diff --git a/src/libnm-core-impl/nm-setting-sriov.c b/src/libnm-core-impl/nm-setting-sriov.c
index 091cc0c6..b9faad56 100644
--- a/src/libnm-core-impl/nm-setting-sriov.c
+++ b/src/libnm-core-impl/nm-setting-sriov.c
@@ -286,7 +286,7 @@ nm_sriov_vf_get_index(const NMSriovVF *vf)
  * nm_sriov_vf_set_attribute:
  * @vf: the #NMSriovVF
  * @name: the name of a route attribute
- * @value: (transfer none) (allow-none): the value
+ * @value: (transfer none) (nullable): the value
  *
  * Sets the named attribute on @vf to the given value.
  *
@@ -364,8 +364,8 @@ const NMVariantAttributeSpec *const _nm_sriov_vf_attribute_spec[] = {
  * nm_sriov_vf_attribute_validate:
  * @name: the attribute name
  * @value: the attribute value
- * @known: (out): on return, whether the attribute name is a known one
- * @error: (allow-none): return location for a #GError, or %NULL
+ * @known: (out) (optional): on return, whether the attribute name is a known one
+ * @error: return location for a #GError, or %NULL
  *
  * Validates a VF attribute, i.e. checks that the attribute is a known one,
  * the value is of the correct type and well-formed.
@@ -533,7 +533,7 @@ vlan_id_compare(gconstpointer a, gconstpointer b, gpointer user_data)
 /**
  * nm_sriov_vf_get_vlan_ids:
  * @vf: the #NMSriovVF
- * @length: (out) (allow-none): on return, the number of VLANs configured
+ * @length: (out) (optional): on return, the number of VLANs configured
  *
  * Returns the VLANs currently configured on the VF. Currently kernel only
  * supports one VLAN per VF.
diff --git a/src/libnm-core-impl/nm-setting-tc-config.c b/src/libnm-core-impl/nm-setting-tc-config.c
index 80135201..c5947e40 100644
--- a/src/libnm-core-impl/nm-setting-tc-config.c
+++ b/src/libnm-core-impl/nm-setting-tc-config.c
@@ -359,7 +359,7 @@ nm_tc_qdisc_get_attribute(NMTCQdisc *qdisc, const char *name)
  * nm_tc_qdisc_set_attribute:
  * @qdisc: the #NMTCQdisc
  * @name: the name of an qdisc attribute
- * @value: (transfer none) (allow-none): the value
+ * @value: (transfer none) (nullable): the value
  *
  * Sets or clears the named attribute on @qdisc to the given value.
  *
@@ -634,7 +634,7 @@ nm_tc_action_get_attribute(NMTCAction *action, const char *name)
  * nm_tc_action_set_attribute:
  * @action: the #NMTCAction
  * @name: the name of an action attribute
- * @value: (transfer none) (allow-none): the value
+ * @value: (transfer none) (nullable): the value
  *
  * Sets or clears the named attribute on @action to the given value.
  *
@@ -1794,6 +1794,12 @@ nm_setting_tc_config_class_init(NMSettingTCConfigClass *klass)
     /* ---nmcli---
      * property: qdiscs
      * format: GPtrArray(NMTCQdisc)
+     * description: Array of TC queueing disciplines. When the "tc" setting is
+     *   present, qdiscs from this property are applied upon activation. If the
+     *   property is empty, all qdiscs are removed and the device will only have
+     *   the default qdisc assigned by kernel according to the
+     *   "net.core.default_qdisc" sysctl. If the "tc" setting is not present,
+     *   NetworkManager doesn't touch the qdiscs present on the interface.
      * description-docbook:
      *  <para>
      *  Array of TC queueing disciplines. qdisc is a basic block in the
@@ -2106,6 +2112,11 @@ nm_setting_tc_config_class_init(NMSettingTCConfigClass *klass)
     /* ---nmcli---
      * property: tfilters
      * format: GPtrArray(NMTCTfilter)
+     * description: Array of TC traffic filters. When the "tc" setting is
+     *   present, filters from this property are applied upon activation. If the
+     *   property is empty, NetworkManager removes all the filters. If the "tc"
+     *   setting is not present, NetworkManager doesn't touch the filters present
+     *   on the interface.
      * description-docbook:
      *  <para>
      *   Array of TC traffic filters. Traffic control can manage the packet content during
diff --git a/src/libnm-core-impl/nm-setting-team.c b/src/libnm-core-impl/nm-setting-team.c
index e6e375e7..83de8b6f 100644
--- a/src/libnm-core-impl/nm-setting-team.c
+++ b/src/libnm-core-impl/nm-setting-team.c
@@ -143,7 +143,7 @@ nm_team_link_watcher_new_ethtool(int delay_up, int delay_down, GError **error)
  * @missed_max: missed_max value
  * @target_host: the host name or the ipv6 address that will be used as
  *   target address in the NS packet
- * @error: (out) (allow-none): location to store the error on failure
+ * @error: location to store the error on failure
  *
  * Creates a new nsna_ping #NMTeamLinkWatcher object
  *
@@ -223,7 +223,7 @@ nm_team_link_watcher_new_nsna_ping(int         init_wait,
  * @source_host: the host name or the ip address that will be used as source
  *   address in the arp request
  * @flags: the watcher #NMTeamLinkWatcherArpPingFlags
- * @error: (out) (allow-none): location to store the error on failure
+ * @error: location to store the error on failure
  *
  * Creates a new arp_ping #NMTeamLinkWatcher object
  *
@@ -261,7 +261,7 @@ nm_team_link_watcher_new_arp_ping(int                           init_wait,
  * @source_host: the host name or the ip address that will be used as source
  *   address in the arp request
  * @flags: the watcher #NMTeamLinkWatcherArpPingFlags
- * @error: (out) (allow-none): location to store the error on failure
+ * @error: location to store the error on failure
  *
  * Creates a new arp_ping #NMTeamLinkWatcher object
  *
diff --git a/src/libnm-core-impl/nm-setting-user.c b/src/libnm-core-impl/nm-setting-user.c
index 70bdc62a..8c2a9942 100644
--- a/src/libnm-core-impl/nm-setting-user.c
+++ b/src/libnm-core-impl/nm-setting-user.c
@@ -268,8 +268,8 @@ nm_setting_user_get_data(NMSettingUser *setting, const char *key)
  * nm_setting_user_set_data:
  * @setting: the #NMSettingUser instance
  * @key: the key to set
- * @val: (allow-none): the value to set or %NULL to clear a key.
- * @error: (allow-none): optional error argument
+ * @val: (nullable): the value to set or %NULL to clear a key.
+ * @error: optional error argument
  *
  * Since: 1.8
  *
diff --git a/src/libnm-core-impl/nm-setting-vlan.c b/src/libnm-core-impl/nm-setting-vlan.c
index 696799d8..e5a99ce9 100644
--- a/src/libnm-core-impl/nm-setting-vlan.c
+++ b/src/libnm-core-impl/nm-setting-vlan.c
@@ -297,8 +297,8 @@ nm_setting_vlan_get_num_priorities(NMSettingVlan *setting, NMVlanPriorityMap map
  * @setting: the #NMSettingVlan
  * @map: the type of priority map
  * @idx: the zero-based index of the ingress/egress priority map entry
- * @out_from: (out) (allow-none): on return the value of the priority map's 'from' item
- * @out_to: (out) (allow-none): on return the value of priority map's 'to' item
+ * @out_from: (out) (optional): on return the value of the priority map's 'from' item
+ * @out_to: (out) (optional): on return the value of priority map's 'to' item
  *
  * Retrieve one of the entries of the #NMSettingVlan:ingress_priority_map
  * or #NMSettingVlan:egress_priority_map properties of this setting.
diff --git a/src/libnm-core-impl/nm-setting-vpn.c b/src/libnm-core-impl/nm-setting-vpn.c
index 4ff98908..5984f773 100644
--- a/src/libnm-core-impl/nm-setting-vpn.c
+++ b/src/libnm-core-impl/nm-setting-vpn.c
@@ -178,7 +178,7 @@ nm_setting_vpn_get_num_data_items(NMSettingVpn *setting)
  * nm_setting_vpn_add_data_item:
  * @setting: the #NMSettingVpn
  * @key: a name that uniquely identifies the given value @item
- * @item: (allow-none): the value to be referenced by @key
+ * @item: (nullable): the value to be referenced by @key
  *
  * Establishes a relationship between @key and @item internally in the
  * setting which may be retrieved later.  Should not be used to store passwords
@@ -227,11 +227,11 @@ nm_setting_vpn_get_data_item(NMSettingVpn *setting, const char *key)
 /**
  * nm_setting_vpn_get_data_keys:
  * @setting: the #NMSettingVpn
- * @out_length: (allow-none) (out): the length of the returned array
+ * @out_length: (out) (optional): the length of the returned array
  *
  * Retrieves every data key inside @setting, as an array.
  *
- * Returns: (array length=out_length) (transfer container): a
+ * Returns: (array length=out_length) (transfer container) (nullable): a
  *   %NULL-terminated array containing each data key or %NULL if
  *   there are no data items.
  *
@@ -346,7 +346,7 @@ nm_setting_vpn_get_num_secrets(NMSettingVpn *setting)
  * nm_setting_vpn_add_secret:
  * @setting: the #NMSettingVpn
  * @key: a name that uniquely identifies the given secret @secret
- * @secret: (allow-none): the secret to be referenced by @key
+ * @secret: (nullable): the secret to be referenced by @key
  *
  * Establishes a relationship between @key and @secret internally in the
  * setting which may be retrieved later.
@@ -394,11 +394,11 @@ nm_setting_vpn_get_secret(NMSettingVpn *setting, const char *key)
 /**
  * nm_setting_vpn_get_secret_keys:
  * @setting: the #NMSettingVpn
- * @out_length: (allow-none) (out): the length of the returned array
+ * @out_length: (out) (optional): the length of the returned array
  *
  * Retrieves every secret key inside @setting, as an array.
  *
- * Returns: (array length=out_length) (transfer container): a
+ * Returns: (array length=out_length) (transfer container) (nullable): a
  *   %NULL-terminated array containing each secret key or %NULL if
  *   there are no secrets.
  *
diff --git a/src/libnm-core-impl/nm-setting-wired.c b/src/libnm-core-impl/nm-setting-wired.c
index 3acd5d97..1ee16df5 100644
--- a/src/libnm-core-impl/nm-setting-wired.c
+++ b/src/libnm-core-impl/nm-setting-wired.c
@@ -507,11 +507,12 @@ nm_setting_wired_get_num_s390_options(NMSettingWired *setting)
  * @setting: the #NMSettingWired
  * @idx: index of the desired option, from 0 to
  * nm_setting_wired_get_num_s390_options() - 1
- * @out_key: (allow-none) (out) (transfer none): on return, the key name of the s390 specific
- *   option; this value is owned by the setting and should not be modified
- * @out_value: (allow-none) (out) (transfer none): on return, the value of the key of the
- *   s390 specific option; this value is owned by the setting and should not be
- *   modified
+ * @out_key: (out) (optional) (nullable) (transfer none): on return, the key
+ *   name of the s390 specific option; this value is owned by the setting and
+ *   should not be modified
+ * @out_value: (out) (optional) (nullable) (transfer none): on return, the value
+ *   of the key of the s390 specific option; this value is owned by the setting
+ *   and should not be modified
  *
  * Given an index, return the value of the s390 option at that index.  indexes
  * are *not* guaranteed to be static across modifications to options done by
@@ -701,7 +702,7 @@ _nm_setting_wired_clear_s390_options(NMSettingWired *setting)
 
 /**
  * nm_setting_wired_get_valid_s390_options:
- * @setting: (allow-none): the #NMSettingWired. This argument is unused
+ * @setting: (nullable): the #NMSettingWired. This argument is unused
  *   and you may pass %NULL.
  *
  * Returns a list of valid s390 options.
diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c
index a981187f..b0e3f66c 100644
--- a/src/libnm-core-impl/nm-setting-wireguard.c
+++ b/src/libnm-core-impl/nm-setting-wireguard.c
@@ -125,7 +125,7 @@ nm_wireguard_peer_new_clone(const NMWireGuardPeer *self, gboolean with_secrets)
 
 /**
  * nm_wireguard_peer_ref:
- * @self: (allow-none): the #NMWireGuardPeer instance
+ * @self: (nullable): the #NMWireGuardPeer instance
  *
  * Returns: returns the input argument @self after incrementing
  *   the reference count.
@@ -150,7 +150,7 @@ nm_wireguard_peer_ref(NMWireGuardPeer *self)
 
 /**
  * nm_wireguard_peer_unref:
- * @self: (allow-none): the #NMWireGuardPeer instance
+ * @self: (nullable): the #NMWireGuardPeer instance
  *
  * Drop a reference to @self. If the last reference is dropped,
  * the instance is freed and all associate data released.
@@ -260,7 +260,7 @@ nm_wireguard_peer_get_public_key(const NMWireGuardPeer *self)
 /**
  * nm_wireguard_peer_set_public_key:
  * @self: the unsealed #NMWireGuardPeer instance
- * @public_key: (allow-none) (transfer none): the new public
+ * @public_key: (nullable) (transfer none): the new public
  *   key or %NULL to clear the public key.
  * @accept_invalid: if %TRUE and @public_key is not %NULL and
  *   invalid, then do not modify the instance.
@@ -339,7 +339,7 @@ nm_wireguard_peer_get_preshared_key(const NMWireGuardPeer *self)
 /**
  * nm_wireguard_peer_set_preshared_key:
  * @self: the unsealed #NMWireGuardPeer instance
- * @preshared_key: (allow-none) (transfer none): the new preshared
+ * @preshared_key: (nullable) (transfer none): the new preshared
  *   key or %NULL to clear the preshared key.
  * @accept_invalid: whether to allow setting the key to an invalid
  *   value. If %FALSE, @self is unchanged if the key is invalid
@@ -564,13 +564,13 @@ nm_wireguard_peer_get_allowed_ips_len(const NMWireGuardPeer *self)
  * @self: the #NMWireGuardPeer instance
  * @idx: the index from zero to (allowed-ips-len - 1) to
  *   retrieve.
- * @out_is_valid: (allow-none): %TRUE if the returned value is a valid allowed-ip
+ * @out_is_valid: (nullable): %TRUE if the returned value is a valid allowed-ip
  *   setting.
  *   This parameter is wrongly not marked as (out) argument, it is
  *   thus not accessible via introspection. This cannot be fixed without
  *   breaking API for introspection users.
  *
- * Returns: (transfer none): the allowed-ip setting at index @idx.
+ * Returns: (transfer none) (nullable): the allowed-ip setting at index @idx.
  *   If @idx is out of range, %NULL will be returned.
  *
  * Since: 1.16
@@ -813,8 +813,8 @@ nm_wireguard_peer_is_valid(const NMWireGuardPeer *self,
 
 /**
  * nm_wireguard_peer_cmp:
- * @a: (allow-none): the #NMWireGuardPeer to compare.
- * @b: (allow-none): the other #NMWireGuardPeer to compare.
+ * @a: (nullable): the #NMWireGuardPeer to compare.
+ * @b: (nullable): the other #NMWireGuardPeer to compare.
  * @compare_flags: #NMSettingCompareFlags to affect the comparison.
  *
  * Returns: zero of the two instances are equivalent or
@@ -1222,11 +1222,11 @@ nm_setting_wireguard_get_peer(NMSettingWireGuard *self, guint idx)
  * @self: the #NMSettingWireGuard instance
  * @public_key: the public key for looking up the
  *   peer.
- * @out_idx: (out) (allow-none): optional output argument
+ * @out_idx: (out) (optional): optional output argument
  *   for the index of the found peer. If no index is found,
  *   this is set to the nm_setting_wireguard_get_peers_len().
  *
- * Returns: (transfer none): the #NMWireGuardPeer instance with a
+ * Returns: (transfer none) (nullable): the #NMWireGuardPeer instance with a
  *   matching public key. If no such peer exists, %NULL is returned.
  *
  * Since: 1.16
diff --git a/src/libnm-core-impl/nm-setting-wireless.c b/src/libnm-core-impl/nm-setting-wireless.c
index 33e16d4b..e2cd1f49 100644
--- a/src/libnm-core-impl/nm-setting-wireless.c
+++ b/src/libnm-core-impl/nm-setting-wireless.c
@@ -392,6 +392,8 @@ nm_setting_wireless_get_bssid(NMSettingWireless *setting)
  * @setting: the #NMSettingWireless
  *
  * Returns: the #NMSettingWireless:rate property of the setting
+ *
+ * Deprecated: 1.44: This setting is not implemented and has no effect.
  **/
 guint32
 nm_setting_wireless_get_rate(NMSettingWireless *setting)
@@ -406,6 +408,8 @@ nm_setting_wireless_get_rate(NMSettingWireless *setting)
  * @setting: the #NMSettingWireless
  *
  * Returns: the #NMSettingWireless:tx-power property of the setting
+ *
+ * Deprecated: 1.44: This setting is not implemented and has no effect.
  **/
 guint32
 nm_setting_wireless_get_tx_power(NMSettingWireless *setting)
@@ -1100,6 +1104,19 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     return NM_SETTING_VERIFY_NORMALIZABLE;
 mac_addr_rand_ok:
 
+    if (priv->tx_power != 0 || priv->rate != 0) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("property is deprecated and not implemented"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_WIRELESS_SETTING_NAME,
+                       priv->tx_power != 0 ? NM_SETTING_WIRELESS_TX_POWER
+                                           : NM_SETTING_WIRELESS_RATE);
+        return NM_SETTING_VERIFY_NORMALIZABLE;
+    }
+
     return TRUE;
 }
 
@@ -1434,15 +1451,14 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
     /**
      * NMSettingWireless:rate:
      *
-     * If non-zero, directs the device to only use the specified bitrate for
-     * communication with the access point.  Units are in Kb/s, ie 5500 = 5.5
-     * Mbit/s.  This property is highly driver dependent and not all devices
-     * support setting a static bitrate.
+     * This property is not implemented and has no effect.
+     *
+     * Deprecated: 1.44: This property is not implemented and has no effect.
      **/
     /* ---ifcfg-rh---
      * property: rate
      * variable: (none)
-     * description: This property is not handled by ifcfg-rh plugin.
+     * description: This property is deprecated and not handled by ifcfg-rh plugin.
      * ---end---
      */
     _nm_setting_property_define_direct_uint32(properties_override,
@@ -1454,19 +1470,20 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
                                               0,
                                               NM_SETTING_PARAM_FUZZY_IGNORE,
                                               NMSettingWirelessPrivate,
-                                              rate);
+                                              rate,
+                                              .is_deprecated = TRUE, );
 
     /**
      * NMSettingWireless:tx-power:
      *
-     * If non-zero, directs the device to use the specified transmit power.
-     * Units are dBm.  This property is highly driver dependent and not all
-     * devices support setting a static transmit power.
+     * This property is not implemented and has no effect.
+     *
+     * Deprecated: 1.44: This property is not implemented and has no effect.
      **/
     /* ---ifcfg-rh---
      * property: tx-power
      * variable: (none)
-     * description: This property is not handled by ifcfg-rh plugin.
+     * description: This property is deprecated and not handled by ifcfg-rh plugin.
      * ---end---
      */
     _nm_setting_property_define_direct_uint32(properties_override,
@@ -1478,7 +1495,8 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
                                               0,
                                               NM_SETTING_PARAM_FUZZY_IGNORE,
                                               NMSettingWirelessPrivate,
-                                              tx_power);
+                                              tx_power,
+                                              .is_deprecated = TRUE, );
 
     /**
      * NMSettingWireless:mac-address:
@@ -1675,11 +1693,16 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
      * property is only meant for reading and reflects the BSSID list of
      * NetworkManager. The changes you make to this property will not be
      * preserved.
+     *
+     * This is not a regular property that the user would configure. Instead,
+     * NetworkManager automatically sets the seen BSSIDs and tracks them internally
+     * in "/var/lib/NetworkManager/seen-bssids" file.
      **/
     /* ---ifcfg-rh---
      * property: seen-bssids
      * variable: (none)
-     * description: This property is not handled by ifcfg-rh plugin.
+     * description: This is not a regular property that would be configured by the
+     *   user. It is not handled by ifcfg-rh plugin.
      * ---end---
      */
     obj_properties[PROP_SEEN_BSSIDS] = g_param_spec_boxed(
@@ -1814,19 +1837,11 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
                                               mac_address_randomization,
                                               .is_deprecated = TRUE, );
 
-    /* Compatibility for deprecated property */
-    /* ---ifcfg-rh---
-     * property: security
-     * variable: (none)
-     * description: This property is deprecated and not handled by ifcfg-rh-plugin.
-     * ---end---
-     */
     /* ---dbus---
      * property: security
-     * description: This property is deprecated, but can be set to the value
-     *   '802-11-wireless-security' when a wireless security setting is also
-     *   present in the connection dictionary, for compatibility with very old
-     *   NetworkManager daemons.
+     * description: This property is deprecated and has no effect.
+     * For backwards compatibility, it can be set to "802-11-wireless-security"
+     * if the profile has a wireless security setting.
      * ---end---
      */
     _nm_properties_override_dbus(
diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c
index 5a46a1b4..276dfd34 100644
--- a/src/libnm-core-impl/nm-setting.c
+++ b/src/libnm-core-impl/nm-setting.c
@@ -110,6 +110,10 @@ nm_setting_lookup_type(const char *name)
 {
     const NMMetaSettingInfo *setting_info;
 
+    /* various callers check whether the result is valid with plain `if (gtype)`.
+     * Assert that G_TYPE_INVALID is zero. */
+    G_STATIC_ASSERT(G_TYPE_INVALID == 0);
+
     g_return_val_if_fail(name, G_TYPE_INVALID);
 
     setting_info = nm_meta_setting_infos_by_name(name);
@@ -2383,7 +2387,7 @@ nm_setting_get_name(NMSetting *setting)
 /**
  * nm_setting_verify:
  * @setting: the #NMSetting to verify
- * @connection: (allow-none): the #NMConnection that @setting came from, or
+ * @connection: (nullable): the #NMConnection that @setting came from, or
  *   %NULL if @setting is being verified in isolation.
  * @error: location to store error, or %NULL
  *
@@ -2421,7 +2425,7 @@ _nm_setting_verify(NMSetting *setting, NMConnection *connection, GError **error)
 /**
  * nm_setting_verify_secrets:
  * @setting: the #NMSetting to verify secrets in
- * @connection: (allow-none): the #NMConnection that @setting came from, or
+ * @connection: (nullable): the #NMConnection that @setting came from, or
  *   %NULL if @setting is being verified in isolation.
  * @error: location to store error, or %NULL
  *
@@ -3828,11 +3832,11 @@ out_zero:
 /**
  * nm_setting_option_get_all_names:
  * @setting: the #NMSetting
- * @out_len: (allow-none) (out):
+ * @out_len: (out) (optional):
  *
  * Gives the name of all set options.
  *
- * Returns: (array length=out_len zero-terminated=1) (transfer none):
+ * Returns: (array length=out_len zero-terminated=1) (transfer none) (nullable):
  *   A %NULL terminated array of key names. If no names are present, this returns
  *   %NULL. The returned array and the names are owned by %NMSetting and might be invalidated
  *   by the next operation.
@@ -3870,7 +3874,7 @@ _nm_setting_option_clear(NMSetting *setting, const char *optname)
 /**
  * nm_setting_option_clear_by_name:
  * @setting: the #NMSetting
- * @predicate: (allow-none) (scope call): the predicate for which names
+ * @predicate: (nullable) (scope call): the predicate for which names
  *   should be clear.
  *   If the predicate returns %TRUE for an option name, the option
  *   gets removed. If %NULL, all options will be removed.
@@ -3937,7 +3941,7 @@ nm_setting_option_get(NMSetting *setting, const char *opt_name)
  * nm_setting_option_get_boolean:
  * @setting: the #NMSetting
  * @opt_name: the option to get
- * @out_value: (allow-none) (out): the optional output value.
+ * @out_value: (out) (nullable): the optional output value.
  *   If the option is unset, %FALSE will be returned.
  *
  * Returns: %TRUE if @opt_name is set to a boolean variant.
@@ -3962,7 +3966,7 @@ nm_setting_option_get_boolean(NMSetting *setting, const char *opt_name, gboolean
  * nm_setting_option_get_uint32:
  * @setting: the #NMSetting
  * @opt_name: the option to get
- * @out_value: (allow-none) (out): the optional output value.
+ * @out_value: (out) (nullable): the optional output value.
  *   If the option is unset, 0 will be returned.
  *
  * Returns: %TRUE if @opt_name is set to a uint32 variant.
@@ -3987,7 +3991,7 @@ nm_setting_option_get_uint32(NMSetting *setting, const char *opt_name, guint32 *
  * nm_setting_option_set:
  * @setting: the #NMSetting
  * @opt_name: the option name to set
- * @variant: (allow-none): the variant to set.
+ * @variant: (nullable): the variant to set.
  *
  * If @variant is %NULL, this clears the option if it is set.
  * Otherwise, @variant is set as the option. If @variant is
@@ -4287,7 +4291,7 @@ nm_range_to_str(const NMRange *range)
 /**
  * nm_range_from_str:
  * @str: the string representation of a range
- * @error: (out) (allow-none): location to store the error on failure
+ * @error: location to store the error on failure
  *
  * Parses the string representation of the range to create a %NMRange
  * instance.
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index c389213f..2bd8936d 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -175,7 +175,7 @@ nm_sock_addr_endpoint_new(const char *endpoint)
 
 /**
  * nm_sock_addr_endpoint_ref:
- * @self: (allow-none): the #NMSockAddrEndpoint
+ * @self: (nullable): the #NMSockAddrEndpoint
  */
 NMSockAddrEndpoint *
 nm_sock_addr_endpoint_ref(NMSockAddrEndpoint *self)
@@ -193,7 +193,7 @@ nm_sock_addr_endpoint_ref(NMSockAddrEndpoint *self)
 
 /**
  * nm_sock_addr_endpoint_unref:
- * @self: (allow-none): the #NMSockAddrEndpoint
+ * @self: (nullable): the #NMSockAddrEndpoint
  */
 void
 nm_sock_addr_endpoint_unref(NMSockAddrEndpoint *self)
@@ -1380,7 +1380,7 @@ nm_utils_ip4_dns_from_variant(GVariant *value)
 /**
  * nm_utils_ip4_addresses_to_variant:
  * @addresses: (element-type NMIPAddress): an array of #NMIPAddress objects
- * @gateway: (allow-none): the gateway IP address
+ * @gateway: (nullable): the gateway IP address
  *
  * Utility function to convert a #GPtrArray of #NMIPAddress objects representing
  * IPv4 addresses into a #GVariant of type 'aau' representing an array of
@@ -1430,7 +1430,8 @@ nm_utils_ip4_addresses_to_variant(GPtrArray *addresses, const char *gateway)
 /**
  * nm_utils_ip4_addresses_from_variant:
  * @value: a #GVariant of type 'aau'
- * @out_gateway: (out) (allow-none) (transfer full): on return, will contain the IP gateway
+ * @out_gateway: (out) (optional) (nullable) (transfer full): on return, will
+ *   contain the IP gateway
  *
  * Utility function to convert a #GVariant of type 'aau' representing a list of
  * NetworkManager IPv4 addresses (which are tuples of address, prefix, and
@@ -1673,7 +1674,7 @@ nm_utils_ip6_dns_from_variant(GVariant *value)
 /**
  * nm_utils_ip6_addresses_to_variant:
  * @addresses: (element-type NMIPAddress): an array of #NMIPAddress objects
- * @gateway: (allow-none): the gateway IP address
+ * @gateway: (nullable): the gateway IP address
  *
  * Utility function to convert a #GPtrArray of #NMIPAddress objects representing
  * IPv6 addresses into a #GVariant of type 'a(ayuay)' representing an array of
@@ -1725,7 +1726,8 @@ nm_utils_ip6_addresses_to_variant(GPtrArray *addresses, const char *gateway)
 /**
  * nm_utils_ip6_addresses_from_variant:
  * @value: a #GVariant of type 'a(ayuay)'
- * @out_gateway: (out) (allow-none) (transfer full): on return, will contain the IP gateway
+ * @out_gateway: (out) (optional) (nullable) (transfer full): on return, will
+ *   contain the IP gateway
  *
  * Utility function to convert a #GVariant of type 'a(ayuay)' representing a
  * list of NetworkManager IPv6 addresses (which are tuples of address, prefix,
@@ -2821,7 +2823,7 @@ extern const NMVariantAttributeSpec *const _nm_sriov_vf_attribute_spec[];
  * nm_utils_sriov_vf_to_str:
  * @vf: the %NMSriovVF
  * @omit_index: if %TRUE, the VF index will be omitted from output string
- * @error: (out) (allow-none): location to store the error on failure
+ * @error: location to store the error on failure
  *
  * Converts a SR-IOV virtual function object to its string representation.
  *
@@ -2982,7 +2984,7 @@ _nm_sriov_vf_parse_vlans(NMSriovVF *vf, const char *str, GError **error)
 /**
  * nm_utils_sriov_vf_from_str:
  * @str: the input string
- * @error: (out) (allow-none): location to store the error on failure
+ * @error: location to store the error on failure
  *
  * Converts a string to a SR-IOV virtual function object.
  *
@@ -3210,17 +3212,17 @@ _nm_utils_check_module_file(const char               *name,
  * nm_utils_file_search_in_paths:
  * @progname: the helper program name, like "iptables"
  *   Must be a non-empty string, without path separator (/).
- * @try_first: (allow-none): a custom path to try first before searching.
+ * @try_first: (nullable): a custom path to try first before searching.
  *   It is silently ignored if it is empty or not an absolute path.
- * @paths: (allow-none): a %NULL terminated list of search paths.
+ * @paths: (nullable): a %NULL terminated list of search paths.
  *   Can be empty or %NULL, in which case only @try_first is checked.
  * @file_test_flags: the flags passed to g_file_test() when searching
  *   for @progname. Set it to 0 to skip the g_file_test().
  * @predicate: (scope call): if given, pass the file name to this function
  *   for additional checks. This check is performed after the check for
  *   @file_test_flags. You cannot omit both @file_test_flags and @predicate.
- * @user_data: (closure) (allow-none): user data for @predicate function.
- * @error: (allow-none): on failure, set a "not found" error %G_IO_ERROR %G_IO_ERROR_NOT_FOUND.
+ * @user_data: (closure) (nullable): user data for @predicate function.
+ * @error: on failure, set a "not found" error %G_IO_ERROR %G_IO_ERROR_NOT_FOUND.
  *
  * Searches for a @progname file in a list of search @paths.
  *
@@ -3595,7 +3597,7 @@ _nm_property_variant_to_gvalue(GVariant *src_value, GValue *dst_value)
  * Before 1.28, it was an error to call this function with any value other than
  * <literal>ARPHRD_ETHER</literal> or <literal>ARPHRD_INFINIBAND</literal>.
  *
- * Return value: the length or zero if the type is unrecognized.
+ * Returns: the length or zero if the type is unrecognized.
  */
 gsize
 nm_utils_hwaddr_len(int type)
@@ -3619,7 +3621,7 @@ nm_utils_hwaddr_len(int type)
  * eg "00:11" or "0:1".  Any "0x" at the beginning of @hex is ignored.  @hex
  * may not start or end with ':'.
  *
- * Return value: (transfer full): the converted bytes, or %NULL on error
+ * Returns: (transfer full): the converted bytes, or %NULL on error
  */
 GBytes *
 nm_utils_hexstr2bin(const char *hex)
@@ -3642,7 +3644,7 @@ nm_utils_hexstr2bin(const char *hex)
  * Parses @asc and converts it to binary form in a #GByteArray. See
  * nm_utils_hwaddr_aton() if you don't want a #GByteArray.
  *
- * Return value: (transfer full): a new #GByteArray, or %NULL if @asc couldn't
+ * Returns: (transfer full): a new #GByteArray, or %NULL if @asc couldn't
  * be parsed
  */
 GByteArray *
@@ -3674,7 +3676,7 @@ fail:
  * Parses @asc and converts it to binary form in @buffer.
  * Bytes in @asc can be separated by colons (:), or hyphens (-), but not mixed.
  *
- * Return value: @buffer, or %NULL if @asc couldn't be parsed
+ * Returns: @buffer, or %NULL if @asc couldn't be parsed
  *   or would be shorter or longer than @length.
  */
 guint8 *
@@ -3697,7 +3699,7 @@ nm_utils_hwaddr_aton(const char *asc, gpointer buffer, gsize length)
  * greater than -1, the returned string is terminated at that index
  * (returned_string[final_len] == '\0'),
  *
- * Return value: (transfer full): the textual form of @bytes
+ * Returns: (transfer full): the textual form of @bytes
  */
 char *
 nm_utils_bin2hexstr(gconstpointer src, gsize len, int final_len)
@@ -3718,7 +3720,7 @@ nm_utils_bin2hexstr(gconstpointer src, gsize len, int final_len)
  *
  * Converts @addr to textual form.
  *
- * Return value: (transfer full): the textual form of @addr
+ * Returns: (transfer full): the textual form of @addr
  */
 char *
 nm_utils_hwaddr_ntoa(gconstpointer addr, gsize length)
@@ -3738,7 +3740,7 @@ nm_utils_hwaddr_ntoa(gconstpointer addr, gsize length)
  * Parses @asc to see if it is a valid hardware address of the given
  * length.
  *
- * Return value: %TRUE if @asc appears to be a valid hardware address
+ * Returns: %TRUE if @asc appears to be a valid hardware address
  *   of the indicated length, %FALSE if not.
  */
 gboolean
@@ -3769,7 +3771,7 @@ nm_utils_hwaddr_valid(const char *asc, gssize length)
  * length, and if so, returns it in canonical form (uppercase, with
  * leading 0s as needed, and with colons rather than hyphens).
  *
- * Return value: (transfer full): the canonicalized address if @asc appears to
+ * Returns: (transfer full): the canonicalized address if @asc appears to
  *   be a valid hardware address of the indicated length, %NULL if not.
  */
 char *
@@ -3830,7 +3832,7 @@ _nm_utils_ipaddr_canonical_or_invalid(int addr_family, const char *ip, gboolean
 /*
  * Determine if given Ethernet address is link-local
  *
- * Return value: %TRUE if @mac is link local
+ * Returns: %TRUE if @mac is link local
  * reserved addr (01:80:c2:00:00:0X) per IEEE 802.1Q 8.6.3 Frame filtering, %FALSE if not.
  */
 gboolean
@@ -3887,7 +3889,7 @@ _nm_utils_hwaddr_link_local_valid(const char *mac)
  * sanity-checked before comparing them against known good addresses; they are
  * guaranteed to not match if they are invalid.)
  *
- * Return value: %TRUE if @hwaddr1 and @hwaddr2 are equivalent, %FALSE if they are
+ * Returns: %TRUE if @hwaddr1 and @hwaddr2 are equivalent, %FALSE if they are
  *   different (or either of them is invalid).
  */
 gboolean
@@ -4296,7 +4298,7 @@ nm_utils_is_valid_iface_name_utf8safe(const char *utf8safe_name)
 
 /**
  * nm_utils_is_valid_iface_name:
- * @name: (allow-none): Name of interface
+ * @name: (nullable): Name of interface
  * @error: location to store the error occurring, or %NULL to ignore
  *
  * Validate the network interface name.
@@ -4321,7 +4323,7 @@ nm_utils_is_valid_iface_name(const char *name, GError **error)
 
 /**
  * nm_utils_iface_valid_name:
- * @name: (allow-none): Name of interface
+ * @name: (nullable): Name of interface
  *
  * Validate the network interface name.
  *
@@ -4340,7 +4342,7 @@ nm_utils_iface_valid_name(const char *name)
 
 /**
  * nm_utils_is_uuid:
- * @str: (allow-none): a string that might be a UUID
+ * @str: (nullable): a string that might be a UUID
  *
  * Checks if @str is a UUID
  *
@@ -4429,7 +4431,7 @@ nm_utils_inet6_ntop(const struct in6_addr *in6addr, char *dst)
  *
  * Checks if @ip contains a valid IP address of the given family.
  *
- * Return value: %TRUE or %FALSE
+ * Returns: %TRUE or %FALSE
  */
 gboolean
 nm_utils_ipaddr_valid(int family, const char *ip)
@@ -4446,7 +4448,7 @@ nm_utils_ipaddr_valid(int family, const char *ip)
  * Checks if @duid string contains either a special duid value ("ll",
  * "llt", "lease" or the "stable" variants) or a valid hex DUID.
  *
- * Return value: %TRUE or %FALSE
+ * Returns: %TRUE or %FALSE
  */
 gboolean
 _nm_utils_dhcp_duid_valid(const char *duid, GBytes **out_duid_bin)
@@ -4711,10 +4713,10 @@ validate_dns_option(const char                 *name,
 /**
  * _nm_utils_dns_option_validate:
  * @option: option string
- * @out_name: (out) (allow-none): the option name
- * @out_value: (out) (allow-none): the option value
+ * @out_name: (out) (optional) (nullable): the option name
+ * @out_value: (out) (optional): the option value
  * @ipv6: whether the option refers to a IPv6 configuration
- * @option_descs: (allow-none): an array of NMUtilsDNSOptionDesc which describes the
+ * @option_descs: (nullable): an array of NMUtilsDNSOptionDesc which describes the
  * valid options
  *
  * Parses a DNS option in the form "name" or "name:number" and, if
@@ -4834,8 +4836,9 @@ nm_utils_enum_to_str(GType type, int value)
  * nm_utils_enum_from_str:
  * @type: the %GType of the enum
  * @str: the input string
- * @out_value: (out) (allow-none): the output value
- * @err_token: (out) (allow-none) (transfer full): location to store the first unrecognized token
+ * @out_value: (out) (optional): the output value
+ * @err_token: (out) (optional) (nullable) (transfer full): location to store
+ *   the first unrecognized token
  *
  * Converts a string to the matching enum value.
  *
@@ -5030,7 +5033,7 @@ _nm_variant_attribute_spec_find_binary_search(const NMVariantAttributeSpec *cons
  * @key_value_separator: character separating key and values
  * @ignore_unknown: whether unknown attributes should be ignored
  * @spec: the attribute format specifiers
- * @error: (out) (allow-none): location to store the error on failure
+ * @error: location to store the error on failure
  *
  * Parse attributes from a string.
  *
@@ -5330,7 +5333,7 @@ NM_UTILS_FLAGS2STR_DEFINE(nm_bluetooth_capability_to_string,
  * @base64_key: the (possibly invalid) base64 encode key.
  * @required_key_len: the expected (binary) length of the key after
  *   decoding. If the length does not match, the validation fails.
- * @out_key: (allow-none): (out): an optional output buffer for the binary
+ * @out_key: (out) (optional): an optional output buffer for the binary
  *   key. If given, it will be filled with exactly @required_key_len
  *   bytes.
  *
diff --git a/src/libnm-core-impl/nm-vpn-editor-plugin.c b/src/libnm-core-impl/nm-vpn-editor-plugin.c
index 3a6abbc2..6c9aff43 100644
--- a/src/libnm-core-impl/nm-vpn-editor-plugin.c
+++ b/src/libnm-core-impl/nm-vpn-editor-plugin.c
@@ -129,7 +129,7 @@ nm_vpn_editor_plugin_get_plugin_info(NMVpnEditorPlugin *plugin)
 /**
  * nm_vpn_editor_plugin_set_plugin_info:
  * @plugin: the #NMVpnEditorPlugin instance
- * @plugin_info: (allow-none): a #NMVpnPluginInfo instance or %NULL
+ * @plugin_info: (nullable): a #NMVpnPluginInfo instance or %NULL
  *
  * Set or clear the plugin-info instance.
  * This takes a weak reference on @plugin_info, to avoid circular
diff --git a/src/libnm-core-impl/nm-vpn-plugin-info.c b/src/libnm-core-impl/nm-vpn-plugin-info.c
index 61e9cde5..d2ce9ed8 100644
--- a/src/libnm-core-impl/nm-vpn-plugin-info.c
+++ b/src/libnm-core-impl/nm-vpn-plugin-info.c
@@ -143,7 +143,7 @@ nm_vpn_plugin_info_check_file_full(const char               *filename,
  *   other users.
  * @check_file: pass a callback to do your own validation.
  * @user_data: user data for @check_file.
- * @error: (allow-none) (out): the error reason if the check fails.
+ * @error: the error reason if the check fails.
  *
  * Check whether the file exists and is a valid name file (in keyfile format).
  * Additionally, also check for file permissions.
@@ -236,7 +236,7 @@ _nm_vpn_plugin_info_get_default_dir_user(void)
  * @check_owner: if set to a non-negative number, check that the file
  *   owner is either the same uid or 0. In that case, also check
  *   that the file is not writable by group or other.
- * @check_file: (allow-none): callback to check whether the file is valid.
+ * @check_file: (nullable): callback to check whether the file is valid.
  * @user_data: data for @check_file
  *
  * Iterate over the content of @dirname and load name files.
@@ -352,16 +352,16 @@ nm_vpn_plugin_info_list_load(void)
 
 /**
  * nm_vpn_plugin_info_new_search_file:
- * @name: (allow-none): the name to search for. Either @name or @service
+ * @name: (nullable): the name to search for. Either @name or @service
  *   must be present.
- * @service: (allow-none): the service to search for. Either @name  or
+ * @service: (nullable): the service to search for. Either @name  or
  *   @service must be present.
  *
  * This has the same effect as doing a full nm_vpn_plugin_info_list_load()
  * followed by a search for the first matching VPN plugin info that has the
  * given @name and/or @service.
  *
- * Returns: (transfer full): a newly created instance of plugin info
+ * Returns: (transfer full) (nullable): a newly created instance of plugin info
  *   or %NULL if no matching value was found.
  *
  * Since: 1.4
@@ -987,7 +987,7 @@ nm_vpn_plugin_info_get_editor_plugin(NMVpnPluginInfo *self)
 /**
  * nm_vpn_plugin_info_set_editor_plugin:
  * @self: plugin info instance
- * @plugin: (allow-none): plugin instance
+ * @plugin: (nullable): plugin instance
  *
  * Set the internal plugin instance. If %NULL, only clear the previous instance.
  *
diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c
index 87f7b042..30b1ca5d 100644
--- a/src/libnm-core-impl/tests/test-general.c
+++ b/src/libnm-core-impl/tests/test-general.c
@@ -7,6 +7,7 @@
 
 #include "libnm-core-impl/nm-default-libnm-core.h"
 
+#include <ctype.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
@@ -87,21 +88,65 @@ G_STATIC_ASSERT(NM_UTILS_HWADDR_LEN_MAX == _NM_UTILS_HWADDR_LEN_MAX);
 static void
 test_nm_ascii_spaces(void)
 {
-    int               i;
-    const char *const S = NM_ASCII_SPACES;
+    int i;
+
+    {
+        const char *const S = NM_ASCII_SPACES;
 
-    for (i = 0; S[i]; i++)
-        g_assert(!strchr(&S[i + 1], S[i]));
+        for (i = 0; S[i]; i++)
+            g_assert(!strchr(&S[i + 1], S[i]));
 
-    for (i = 0; S[i] != '\0'; i++)
-        g_assert(g_ascii_isspace(S[i]));
+        for (i = 0; S[i] != '\0'; i++)
+            g_assert(g_ascii_isspace(S[i]));
 
-    g_assert(!g_ascii_isspace((char) 0));
-    for (i = 1; i < 0x100; i++) {
-        if (g_ascii_isspace((char) i))
-            g_assert(strchr(S, (char) i));
-        else
-            g_assert(!strchr(S, (char) i));
+        g_assert(!g_ascii_isspace((char) 0));
+        for (i = 1; i < 0x100; i++) {
+            g_assert((!!g_ascii_isspace((char) i)) == (!!strchr(S, (char) i)));
+        }
+    }
+
+    {
+        const char *const S = NM_ASCII_WHITESPACES;
+
+        for (i = 0; S[i]; i++)
+            g_assert(!strchr(&S[i + 1], S[i]));
+
+        for (i = 0; S[i] != '\0'; i++)
+            g_assert(nm_ascii_is_whitespace(S[i]));
+
+        g_assert(!nm_ascii_is_whitespace((char) 0));
+        for (i = 1; i < 0x100; i++) {
+            g_assert(nm_ascii_is_whitespace((char) i) == (!!strchr(S, (char) i)));
+        }
+    }
+
+    {
+        const char *const S = NM_ASCII_SPACES_CTYPE;
+
+        for (i = 0; S[i]; i++)
+            g_assert(!strchr(&S[i + 1], S[i]));
+
+        if (nm_streq0(g_getenv("LANG"), "C")) {
+            g_assert(!isspace((char) 0));
+            for (i = 1; i < 0x100; i++) {
+                g_assert((!!isspace((char) i)) == (!!strchr(S, (char) i)));
+            }
+        }
+    }
+
+    {
+        const char *const S = NM_ASCII_SPACES_KERNEL;
+
+        for (i = 0; S[i]; i++)
+            g_assert(!strchr(&S[i + 1], S[i]));
+
+        for (i = 0; S[i] != '\0'; i++)
+            g_assert(nm_ascii_is_space_kernel(S[i]));
+
+        g_assert(!nm_ascii_is_space_kernel((char) 0));
+        for (i = 1; i < 0x100; i++) {
+            g_assert(nm_ascii_is_space_kernel((char) i) == (!!strchr(S, (char) i)));
+        }
     }
 }
 
@@ -1417,6 +1462,7 @@ _do_test_c_list_sort(CListSort *elements, guint n_list, gboolean headless)
 
     g_assert(!c_list_is_empty(&head));
     g_assert(c_list_length(&head) == n_list);
+    g_assert(c_list_is_empty_or_single(&head) == (n_list <= 1));
 
     el_prev = NULL;
     c_list_for_each (iter, &head) {
@@ -1443,6 +1489,10 @@ test_c_list_sort(void)
     guint              n_list;
     guint              repeat;
 
+    g_assert(!c_list_is_linked(NULL));
+    g_assert(c_list_is_empty(NULL));
+    g_assert(c_list_is_empty_or_single(NULL));
+
     {
         CList head;
 
@@ -1450,6 +1500,7 @@ test_c_list_sort(void)
         c_list_sort(&head, _c_list_sort_cmp, NULL);
         g_assert(c_list_length(&head) == 0);
         g_assert(c_list_is_empty(&head));
+        g_assert(c_list_is_empty_or_single(&head));
     }
 
     elements = g_new0(CListSort, N_ELEMENTS);
@@ -1517,6 +1568,8 @@ _do_test_c_list_insert_sorted(CListSort *elements, guint n_list, bool append_equ
     g_assert(c_list_length_is(&head, n_list));
     g_assert(!c_list_length_is(&head, n_list + 1));
 
+    g_assert(c_list_is_empty_or_single(&head) == (n_list <= 1));
+
     el_prev = NULL;
     c_list_for_each_entry (el, &head, lst) {
         if (el_prev) {
@@ -3202,7 +3255,7 @@ test_setting_new_from_dbus_bad(void)
     nmtst_assert_success(conn, error);
     setting = nm_connection_get_setting(conn, NM_TYPE_SETTING_WIRELESS);
     g_assert(setting);
-    g_assert_cmpint(nm_setting_wireless_get_rate(NM_SETTING_WIRELESS(setting)), ==, 10);
+    g_assert_cmpint(nm_setting_wireless_get_rate(NM_SETTING_WIRELESS(setting)), ==, 0);
     g_object_unref(conn);
     g_variant_unref(dict);
 
@@ -6247,14 +6300,16 @@ test_connection_normalize_infiniband(void)
     nmtst_connection_normalize(con);
     g_assert_cmpstr(nm_connection_get_interface_name(con), ==, "x234567890123.0");
 
-#define iface_name(parent, p_key, expected)                                                        \
-    G_STMT_START                                                                                   \
-    {                                                                                              \
-        gs_free char *_s = nm_setting_infiniband_create_virtual_interface_name((parent), (p_key)); \
-                                                                                                   \
-        g_assert(nm_utils_ifname_valid_kernel(_s, NULL));                                          \
-        g_assert_cmpstr(_s, ==, (expected));                                                       \
-    }                                                                                              \
+#define iface_name(parent, p_key, expected)                  \
+    G_STMT_START                                             \
+    {                                                        \
+        char _name[NM_IFNAMSIZ];                             \
+                                                             \
+        nm_net_devname_infiniband(_name, (parent), (p_key)); \
+                                                             \
+        g_assert(nm_utils_ifname_valid_kernel(_name, NULL)); \
+        g_assert_cmpstr(_name, ==, (expected));              \
+    }                                                        \
     G_STMT_END
 
     iface_name("foo", 15, "foo.000f");
@@ -10515,7 +10570,7 @@ test_nm_ip_addr_zero(void)
     g_assert_cmpstr(nm_inet_ntop(AF_INET, &nm_ip_addr_zero, buf), ==, "0.0.0.0");
     g_assert_cmpstr(nm_inet_ntop(AF_INET6, &nm_ip_addr_zero, buf), ==, "::");
 
-    G_STATIC_ASSERT_EXPR(sizeof(a) == sizeof(a.array));
+    G_STATIC_ASSERT_EXPR(sizeof(a) == sizeof(a.addr_ptr));
 }
 
 static void
diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c
index 09e0fc70..b30758ff 100644
--- a/src/libnm-core-impl/tests/test-setting.c
+++ b/src/libnm-core-impl/tests/test-setting.c
@@ -121,8 +121,6 @@ test_nm_meta_setting_types_by_priority(void)
     G_STATIC_ASSERT_EXPR(_NM_META_SETTING_TYPE_NUM
                          == G_N_ELEMENTS(nm_meta_setting_types_by_priority));
 
-    G_STATIC_ASSERT_EXPR(_NM_META_SETTING_TYPE_NUM == 54);
-
     arr = g_ptr_array_new_with_free_func(g_object_unref);
 
     for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) {
@@ -5261,6 +5259,7 @@ test_bond_meta(void)
     _A(_nm_setting_bond_opt_value_as_u16, set, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, 0, EINVAL);
     _A(_nm_setting_bond_opt_value_as_u16, set, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, 0, EINVAL);
     _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, 1, 0);
+    _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, 0, 0);
     _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, 0, 0);
     _A(_nm_setting_bond_opt_value_as_intbool, set, NM_SETTING_BOND_OPTION_USE_CARRIER, 1, 0);
     _A(_nm_setting_bond_opt_value_as_intbool,
diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h
index ce27e8e0..dbb5a7fa 100644
--- a/src/libnm-core-intern/nm-core-internal.h
+++ b/src/libnm-core-intern/nm-core-internal.h
@@ -43,6 +43,7 @@
 #include "nm-setting-ip-tunnel.h"
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-ip6-config.h"
+#include "nm-setting-link.h"
 #include "nm-setting-loopback.h"
 #include "nm-setting-macsec.h"
 #include "nm-setting-macvlan.h"
@@ -322,8 +323,6 @@ typedef gpointer (*NMUtilsCopyFunc)(gpointer);
 const char **
 _nm_ip_address_get_attribute_names(const NMIPAddress *addr, gboolean sorted, guint *out_length);
 
-char *nm_setting_infiniband_create_virtual_interface_name(const char *parent, int p_key);
-
 #define NM_SETTING_WIRED_S390_OPTION_MAX_LEN 200u
 
 void     _nm_setting_wired_clear_s390_options(NMSettingWired *setting);
@@ -456,6 +455,20 @@ _nm_connection_get_setting(NMConnection *connection, GType type)
     return (gpointer) nm_connection_get_setting(connection, type);
 }
 
+gpointer _nm_connection_get_setting_by_metatype_unsafe(NMConnection     *connection,
+                                                       NMMetaSettingType meta_type);
+
+/* This variant is the most efficient one, because it does not require resolving a
+ * name/GType first. The NMMetaSettingType enum allows for a direct lookup. */
+#define _nm_connection_get_setting_by_metatype(connection, meta_type)                       \
+    ({                                                                                      \
+        /* Static assert that meta_type is in the valid range. If you don't want that,
+         * because the argument is no a compile time constant, use  _nm_connection_get_setting_by_metatype_unsafe(). */      \
+        G_STATIC_ASSERT((meta_type) < _NM_META_SETTING_TYPE_NUM && ((int) meta_type) >= 0); \
+                                                                                            \
+        _nm_connection_get_setting_by_metatype_unsafe((connection), (meta_type));           \
+    })
+
 NMSettingIPConfig *nm_connection_get_setting_ip_config(NMConnection *connection, int addr_family);
 
 /*****************************************************************************/
@@ -472,6 +485,7 @@ typedef enum {
     NM_BOND_OPTION_TYPE_INT,
     NM_BOND_OPTION_TYPE_BOTH,
     NM_BOND_OPTION_TYPE_IP,
+    NM_BOND_OPTION_TYPE_IP6,
     NM_BOND_OPTION_TYPE_MAC,
     NM_BOND_OPTION_TYPE_IFNAME,
 } NMBondOptionType;
@@ -1023,6 +1037,8 @@ _nm_connection_serialize_secrets(NMConnectionSerializationFlags flags,
 void _nm_connection_clear_secrets_by_secret_flags(NMConnection        *self,
                                                   NMSettingSecretFlags filter_flags);
 
+typedef gboolean (*_NMConnectionForEachSecretFunc)(NMSettingSecretFlags flags, gpointer user_data);
+
 GVariant *_nm_connection_for_each_secret(NMConnection                  *self,
                                          GVariant                      *secrets,
                                          gboolean                       remove_non_secrets,
diff --git a/src/libnm-core-intern/nm-meta-setting-base-impl.h b/src/libnm-core-intern/nm-meta-setting-base-impl.h
index 0c2def90..c6d1c2fc 100644
--- a/src/libnm-core-intern/nm-meta-setting-base-impl.h
+++ b/src/libnm-core-intern/nm-meta-setting-base-impl.h
@@ -127,6 +127,7 @@ typedef enum _nm_packed {
     NM_META_SETTING_TYPE_IP_TUNNEL,
     NM_META_SETTING_TYPE_IP4_CONFIG,
     NM_META_SETTING_TYPE_IP6_CONFIG,
+    NM_META_SETTING_TYPE_LINK,
     NM_META_SETTING_TYPE_LOOPBACK,
     NM_META_SETTING_TYPE_MACSEC,
     NM_META_SETTING_TYPE_MACVLAN,
diff --git a/src/libnm-core-public/meson.build b/src/libnm-core-public/meson.build
index c5407130..d135dbff 100644
--- a/src/libnm-core-public/meson.build
+++ b/src/libnm-core-public/meson.build
@@ -29,6 +29,7 @@ libnm_core_headers = files(
   'nm-setting-ip-tunnel.h',
   'nm-setting-ip4-config.h',
   'nm-setting-ip6-config.h',
+  'nm-setting-link.h',
   'nm-setting-loopback.h',
   'nm-setting-macsec.h',
   'nm-setting-macvlan.h',
diff --git a/src/libnm-core-public/nm-core-types.h b/src/libnm-core-public/nm-core-types.h
index f285a0f6..758733a1 100644
--- a/src/libnm-core-public/nm-core-types.h
+++ b/src/libnm-core-public/nm-core-types.h
@@ -35,6 +35,7 @@ typedef struct _NMSettingIP6Config        NMSettingIP6Config;
 typedef struct _NMSettingIPConfig         NMSettingIPConfig;
 typedef struct _NMSettingIPTunnel         NMSettingIPTunnel;
 typedef struct _NMSettingInfiniband       NMSettingInfiniband;
+typedef struct _NMSettingLink             NMSettingLink;
 typedef struct _NMSettingLoopback         NMSettingLoopback;
 typedef struct _NMSettingMacsec           NMSettingMacsec;
 typedef struct _NMSettingMacvlan          NMSettingMacvlan;
diff --git a/src/libnm-core-public/nm-dbus-interface.h b/src/libnm-core-public/nm-dbus-interface.h
index 9f64f75f..b490e3b1 100644
--- a/src/libnm-core-public/nm-dbus-interface.h
+++ b/src/libnm-core-public/nm-dbus-interface.h
@@ -447,7 +447,7 @@ typedef enum /*< flags >*/ {
  * @NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS: modem supports at least one of GSM,
  * GPRS, EDGE, UMTS, HSDPA, HSUPA, or HSPA+ packet switched data capability
  * @NM_DEVICE_MODEM_CAPABILITY_LTE: modem has LTE data capability
- * @NM_DEVICE_MODEM_CAPABILITY_5GNR: modem has 5GNR data capability (Since: 1.36)
+ * @NM_DEVICE_MODEM_CAPABILITY_5GNR: modem has 5GNR data capability. Since: 1.36.
  *
  * #NMDeviceModemCapabilities values indicate the generic radio access
  * technology families a modem device supports.  For more information on the
@@ -986,7 +986,7 @@ typedef enum {
  *
  * The flags for CheckpointCreate call
  *
- * Since: 1.12 (public since 1.4, g-ir since 1.12)
+ * Since: 1.12: Public since 1.4, g-ir since 1.12.
  */
 typedef enum /*< flags >*/ {
     NM_CHECKPOINT_CREATE_FLAG_NONE                       = 0,
diff --git a/src/libnm-core-public/nm-dbus-types.xml b/src/libnm-core-public/nm-dbus-types.xml
index db5d8a8b..d5ac04b4 100644
--- a/src/libnm-core-public/nm-dbus-types.xml
+++ b/src/libnm-core-public/nm-dbus-types.xml
@@ -750,7 +750,7 @@
             <row role="constant">
               <entry role="enum_member_name"><para>NM_DEVICE_MODEM_CAPABILITY_5GNR</para><para></para></entry>
               <entry role="enum_member_value"><para>= <literal>0x00000040</literal></para><para></para></entry>
-              <entry role="enum_member_description"><para>modem has 5GNR data capability (Since: 1.36)</para><para></para></entry>
+              <entry role="enum_member_description"><para>modem has 5GNR data capability. Since: 1.36.</para><para></para></entry>
             </row>
           </tbody>
         </tgroup>
@@ -1638,7 +1638,7 @@
     <indexterm zone="NMCheckpointCreateFlags">
       <primary>NMCheckpointCreateFlags</primary>
     </indexterm>
-    <para><para>The flags for CheckpointCreate call</para><para>Since: 1.12 (public since 1.4, g-ir since 1.12)</para><para></para></para>
+    <para><para>The flags for CheckpointCreate call</para><para>Since: 1.12: Public since 1.4, g-ir since 1.12.</para><para></para></para>
     <refsect3 role="enum_members">
       <title>Values</title>
       <informaltable role="enum_members_table" pgwide="1" frame="none">
diff --git a/src/libnm-core-public/nm-errors.h b/src/libnm-core-public/nm-errors.h
index 639c508e..11ce25f1 100644
--- a/src/libnm-core-public/nm-errors.h
+++ b/src/libnm-core-public/nm-errors.h
@@ -250,6 +250,12 @@ GQuark nm_secret_agent_error_quark(void);
  * @NM_SETTINGS_ERROR_UUID_EXISTS: a connection with that UUID already exists
  * @NM_SETTINGS_ERROR_INVALID_HOSTNAME: attempted to set an invalid hostname
  * @NM_SETTINGS_ERROR_INVALID_ARGUMENTS: invalid arguments
+ * @NM_SETTINGS_ERROR_VERSION_ID_MISMATCH: The profile's VersionId mismatched
+ *   and the update is rejected. See the "version-id" argument to Update2()
+ *   method. Since 1.44.
+ * @NM_SETTINGS_ERROR_NOT_SUPPORTED_BY_PLUGIN: the requested operation is not
+ *   supported by the settings plugin currently in use for the specified object.
+ *   Since: 1.44.
  *
  * Errors related to the settings/persistent configuration interface of
  * NetworkManager.
@@ -259,14 +265,16 @@ GQuark nm_secret_agent_error_quark(void);
  * D-Bus errors in that namespace.
  */
 typedef enum {
-    NM_SETTINGS_ERROR_FAILED = 0,           /*< nick=Failed >*/
-    NM_SETTINGS_ERROR_PERMISSION_DENIED,    /*< nick=PermissionDenied >*/
-    NM_SETTINGS_ERROR_NOT_SUPPORTED,        /*< nick=NotSupported >*/
-    NM_SETTINGS_ERROR_INVALID_CONNECTION,   /*< nick=InvalidConnection >*/
-    NM_SETTINGS_ERROR_READ_ONLY_CONNECTION, /*< nick=ReadOnlyConnection >*/
-    NM_SETTINGS_ERROR_UUID_EXISTS,          /*< nick=UuidExists >*/
-    NM_SETTINGS_ERROR_INVALID_HOSTNAME,     /*< nick=InvalidHostname >*/
-    NM_SETTINGS_ERROR_INVALID_ARGUMENTS,    /*< nick=InvalidArguments >*/
+    NM_SETTINGS_ERROR_FAILED = 0,              /*< nick=Failed >*/
+    NM_SETTINGS_ERROR_PERMISSION_DENIED,       /*< nick=PermissionDenied >*/
+    NM_SETTINGS_ERROR_NOT_SUPPORTED,           /*< nick=NotSupported >*/
+    NM_SETTINGS_ERROR_INVALID_CONNECTION,      /*< nick=InvalidConnection >*/
+    NM_SETTINGS_ERROR_READ_ONLY_CONNECTION,    /*< nick=ReadOnlyConnection >*/
+    NM_SETTINGS_ERROR_UUID_EXISTS,             /*< nick=UuidExists >*/
+    NM_SETTINGS_ERROR_INVALID_HOSTNAME,        /*< nick=InvalidHostname >*/
+    NM_SETTINGS_ERROR_INVALID_ARGUMENTS,       /*< nick=InvalidArguments >*/
+    NM_SETTINGS_ERROR_VERSION_ID_MISMATCH,     /*< nick=VersionIdMismatch >*/
+    NM_SETTINGS_ERROR_NOT_SUPPORTED_BY_PLUGIN, /*< nick=NotSupportedByPlugin >*/
 } NMSettingsError;
 
 GQuark nm_settings_error_quark(void);
diff --git a/src/libnm-core-public/nm-setting-bond-port.h b/src/libnm-core-public/nm-setting-bond-port.h
index 29b0beb0..033c59b4 100644
--- a/src/libnm-core-public/nm-setting-bond-port.h
+++ b/src/libnm-core-public/nm-setting-bond-port.h
@@ -42,7 +42,7 @@ NMSetting *nm_setting_bond_port_new(void);
 NM_AVAILABLE_IN_1_34
 guint32 nm_setting_bond_port_get_queue_id(NMSettingBondPort *setting);
 
-NM_AVAILABLE_IN_1_42_8
+NM_AVAILABLE_IN_1_44
 gint32 nm_setting_bond_port_get_prio(NMSettingBondPort *setting);
 
 G_END_DECLS
diff --git a/src/libnm-core-public/nm-setting-bond.h b/src/libnm-core-public/nm-setting-bond.h
index 10d703bc..7cead175 100644
--- a/src/libnm-core-public/nm-setting-bond.h
+++ b/src/libnm-core-public/nm-setting-bond.h
@@ -58,6 +58,9 @@ G_BEGIN_DECLS
 #define NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB    "tlb_dynamic_lb"
 #define NM_SETTING_BOND_OPTION_LP_INTERVAL       "lp_interval"
 #define NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY  "peer_notif_delay"
+#define NM_SETTING_BOND_OPTION_ARP_MISSED_MAX    "arp_missed_max"
+#define NM_SETTING_BOND_OPTION_LACP_ACTIVE       "lacp_active"
+#define NM_SETTING_BOND_OPTION_NS_IP6_TARGET     "ns_ip6_target"
 
 typedef struct _NMSettingBondClass NMSettingBondClass;
 
diff --git a/src/libnm-core-public/nm-setting-connection.h b/src/libnm-core-public/nm-setting-connection.h
index 875307bd..42dff205 100644
--- a/src/libnm-core-public/nm-setting-connection.h
+++ b/src/libnm-core-public/nm-setting-connection.h
@@ -170,7 +170,9 @@ int nm_setting_connection_get_autoconnect_retries(NMSettingConnection *setting);
 NM_AVAILABLE_IN_1_14
 NMConnectionMultiConnect nm_setting_connection_get_multi_connect(NMSettingConnection *setting);
 guint64                  nm_setting_connection_get_timestamp(NMSettingConnection *setting);
-gboolean                 nm_setting_connection_get_read_only(NMSettingConnection *setting);
+
+NM_DEPRECATED_IN_1_44
+gboolean nm_setting_connection_get_read_only(NMSettingConnection *setting);
 
 guint32     nm_setting_connection_get_num_permissions(NMSettingConnection *setting);
 gboolean    nm_setting_connection_get_permission(NMSettingConnection *setting,
diff --git a/src/libnm-core-public/nm-setting-gsm.h b/src/libnm-core-public/nm-setting-gsm.h
index 0d57511d..dd5b0b26 100644
--- a/src/libnm-core-public/nm-setting-gsm.h
+++ b/src/libnm-core-public/nm-setting-gsm.h
@@ -26,19 +26,21 @@ G_BEGIN_DECLS
 
 #define NM_SETTING_GSM_SETTING_NAME "gsm"
 
-#define NM_SETTING_GSM_AUTO_CONFIG     "auto-config"
-#define NM_SETTING_GSM_USERNAME        "username"
-#define NM_SETTING_GSM_PASSWORD        "password"
-#define NM_SETTING_GSM_PASSWORD_FLAGS  "password-flags"
-#define NM_SETTING_GSM_APN             "apn"
-#define NM_SETTING_GSM_NETWORK_ID      "network-id"
-#define NM_SETTING_GSM_PIN             "pin"
-#define NM_SETTING_GSM_PIN_FLAGS       "pin-flags"
-#define NM_SETTING_GSM_HOME_ONLY       "home-only"
-#define NM_SETTING_GSM_DEVICE_ID       "device-id"
-#define NM_SETTING_GSM_SIM_ID          "sim-id"
-#define NM_SETTING_GSM_SIM_OPERATOR_ID "sim-operator-id"
-#define NM_SETTING_GSM_MTU             "mtu"
+#define NM_SETTING_GSM_AUTO_CONFIG                  "auto-config"
+#define NM_SETTING_GSM_USERNAME                     "username"
+#define NM_SETTING_GSM_PASSWORD                     "password"
+#define NM_SETTING_GSM_PASSWORD_FLAGS               "password-flags"
+#define NM_SETTING_GSM_APN                          "apn"
+#define NM_SETTING_GSM_NETWORK_ID                   "network-id"
+#define NM_SETTING_GSM_PIN                          "pin"
+#define NM_SETTING_GSM_PIN_FLAGS                    "pin-flags"
+#define NM_SETTING_GSM_HOME_ONLY                    "home-only"
+#define NM_SETTING_GSM_DEVICE_ID                    "device-id"
+#define NM_SETTING_GSM_SIM_ID                       "sim-id"
+#define NM_SETTING_GSM_SIM_OPERATOR_ID              "sim-operator-id"
+#define NM_SETTING_GSM_MTU                          "mtu"
+#define NM_SETTING_GSM_INITIAL_EPS_BEARER_CONFIGURE "initial-eps-bearer-configure"
+#define NM_SETTING_GSM_INITIAL_EPS_BEARER_APN       "initial-eps-bearer-apn"
 
 /* Deprecated */
 #define NM_SETTING_GSM_NUMBER "number"
@@ -67,6 +69,10 @@ NM_AVAILABLE_IN_1_2
 const char *nm_setting_gsm_get_sim_operator_id(NMSettingGsm *setting);
 NM_AVAILABLE_IN_1_8
 guint32 nm_setting_gsm_get_mtu(NMSettingGsm *setting);
+NM_AVAILABLE_IN_1_44
+gboolean nm_setting_gsm_get_initial_eps_config(NMSettingGsm *setting);
+NM_AVAILABLE_IN_1_44
+const char *nm_setting_gsm_get_initial_eps_apn(NMSettingGsm *setting);
 
 NM_DEPRECATED_IN_1_16
 const char *nm_setting_gsm_get_number(NMSettingGsm *setting);
diff --git a/src/libnm-core-public/nm-setting-ip-config.h b/src/libnm-core-public/nm-setting-ip-config.h
index 7e1d3889..f7281965 100644
--- a/src/libnm-core-public/nm-setting-ip-config.h
+++ b/src/libnm-core-public/nm-setting-ip-config.h
@@ -501,7 +501,7 @@ NM_AVAILABLE_IN_1_28
 void nm_setting_ip_config_clear_dhcp_reject_servers(NMSettingIPConfig *setting);
 NM_AVAILABLE_IN_1_42
 NMTernary nm_setting_ip_config_get_auto_route_ext_gw(NMSettingIPConfig *setting);
-NM_AVAILABLE_IN_1_42_2
+NM_AVAILABLE_IN_1_44
 NMTernary nm_setting_ip_config_get_replace_local_rule(NMSettingIPConfig *setting);
 
 G_END_DECLS
diff --git a/src/libnm-core-public/nm-setting-ip6-config.h b/src/libnm-core-public/nm-setting-ip6-config.h
index 5abc173d..0403cdf8 100644
--- a/src/libnm-core-public/nm-setting-ip6-config.h
+++ b/src/libnm-core-public/nm-setting-ip6-config.h
@@ -40,6 +40,8 @@ G_BEGIN_DECLS
 
 #define NM_SETTING_IP6_CONFIG_MTU "mtu"
 
+#define NM_SETTING_IP6_CONFIG_DHCP_PD_HINT "dhcp-pd-hint"
+
 /**
  * NM_SETTING_IP6_CONFIG_METHOD_IGNORE:
  *
@@ -164,6 +166,8 @@ NM_AVAILABLE_IN_1_24
 gint32 nm_setting_ip6_config_get_ra_timeout(NMSettingIP6Config *setting);
 NM_AVAILABLE_IN_1_40
 guint32 nm_setting_ip6_config_get_mtu(NMSettingIP6Config *setting);
+NM_AVAILABLE_IN_1_44
+const char *nm_setting_ip6_config_get_dhcp_pd_hint(NMSettingIP6Config *setting);
 
 G_END_DECLS
 
diff --git a/src/libnm-core-public/nm-setting-link.h b/src/libnm-core-public/nm-setting-link.h
new file mode 100644
index 00000000..98973ff5
--- /dev/null
+++ b/src/libnm-core-public/nm-setting-link.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2020 Red Hat, Inc.
+ */
+
+#ifndef __NM_SETTING_LINK_H__
+#define __NM_SETTING_LINK_H__
+
+#if !defined(__NETWORKMANAGER_H_INSIDE__) && !defined(NETWORKMANAGER_COMPILATION)
+#error "Only <NetworkManager.h> can be included directly."
+#endif
+
+#include "nm-setting.h"
+
+G_BEGIN_DECLS
+
+#define NM_TYPE_SETTING_LINK (nm_setting_link_get_type())
+#define NM_SETTING_LINK(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_SETTING_LINK, NMSettingLink))
+#define NM_SETTING_LINK_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_SETTING_LINK, NMSettingLinkClass))
+#define NM_IS_SETTING_LINK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_SETTING_LINK))
+#define NM_IS_SETTING_LINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_SETTING_LINK))
+#define NM_SETTING_LINK_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_SETTING_LINK, NMSettingLinkClass))
+
+#define NM_SETTING_LINK_SETTING_NAME "link"
+
+#define NM_SETTING_LINK_TX_QUEUE_LENGTH  "tx-queue-length"
+#define NM_SETTING_LINK_GSO_MAX_SIZE     "gso-max-size"
+#define NM_SETTING_LINK_GSO_MAX_SEGMENTS "gso-max-segments"
+#define NM_SETTING_LINK_GRO_MAX_SIZE     "gro-max-size"
+
+typedef struct _NMSettingLinkClass NMSettingLinkClass;
+
+NM_AVAILABLE_IN_1_44
+GType nm_setting_link_get_type(void);
+NM_AVAILABLE_IN_1_44
+NMSetting *nm_setting_link_new(void);
+
+NM_AVAILABLE_IN_1_44
+gint64 nm_setting_link_get_tx_queue_length(NMSettingLink *setting);
+NM_AVAILABLE_IN_1_44
+gint64 nm_setting_link_get_gso_max_size(NMSettingLink *setting);
+NM_AVAILABLE_IN_1_44
+gint64 nm_setting_link_get_gso_max_segments(NMSettingLink *setting);
+NM_AVAILABLE_IN_1_44
+gint64 nm_setting_link_get_gro_max_size(NMSettingLink *setting);
+
+G_END_DECLS
+
+#endif /* __NM_SETTING_LINK_H__ */
diff --git a/src/libnm-core-public/nm-setting-wireless.h b/src/libnm-core-public/nm-setting-wireless.h
index bb158112..5f8d09c5 100644
--- a/src/libnm-core-public/nm-setting-wireless.h
+++ b/src/libnm-core-public/nm-setting-wireless.h
@@ -155,8 +155,13 @@ const char *nm_setting_wireless_get_mode(NMSettingWireless *setting);
 const char *nm_setting_wireless_get_band(NMSettingWireless *setting);
 guint32     nm_setting_wireless_get_channel(NMSettingWireless *setting);
 const char *nm_setting_wireless_get_bssid(NMSettingWireless *setting);
-guint32     nm_setting_wireless_get_rate(NMSettingWireless *setting);
-guint32     nm_setting_wireless_get_tx_power(NMSettingWireless *setting);
+
+NM_DEPRECATED_IN_1_44
+guint32 nm_setting_wireless_get_rate(NMSettingWireless *setting);
+
+NM_DEPRECATED_IN_1_44
+guint32 nm_setting_wireless_get_tx_power(NMSettingWireless *setting);
+
 const char *nm_setting_wireless_get_mac_address(NMSettingWireless *setting);
 const char *nm_setting_wireless_get_cloned_mac_address(NMSettingWireless *setting);
 
diff --git a/src/libnm-core-public/nm-setting.h b/src/libnm-core-public/nm-setting.h
index 737cf883..d525a6ad 100644
--- a/src/libnm-core-public/nm-setting.h
+++ b/src/libnm-core-public/nm-setting.h
@@ -164,9 +164,6 @@ typedef void (*NMSettingValueIterFn)(NMSetting    *setting,
                                      GParamFlags   flags,
                                      gpointer      user_data);
 
-/*< private >*/
-typedef gboolean (*_NMConnectionForEachSecretFunc)(NMSettingSecretFlags flags, gpointer user_data);
-
 typedef struct _NMSettingClass NMSettingClass;
 
 GType nm_setting_get_type(void);
diff --git a/src/libnm-core-public/nm-version-macros.h b/src/libnm-core-public/nm-version-macros.h
index 40c89169..9b5233e4 100644
--- a/src/libnm-core-public/nm-version-macros.h
+++ b/src/libnm-core-public/nm-version-macros.h
@@ -22,7 +22,7 @@
  * Evaluates to the minor version number of NetworkManager which this source
  * is compiled against.
  */
-#define NM_MINOR_VERSION (42)
+#define NM_MINOR_VERSION (44)
 
 /**
  * NM_MICRO_VERSION:
@@ -30,7 +30,7 @@
  * Evaluates to the micro version number of NetworkManager which this source
  * compiled against.
  */
-#define NM_MICRO_VERSION (8)
+#define NM_MICRO_VERSION (0)
 
 /**
  * NM_CHECK_VERSION:
@@ -72,8 +72,7 @@
 #define NM_VERSION_1_38   (NM_ENCODE_VERSION(1, 38, 0))
 #define NM_VERSION_1_40   (NM_ENCODE_VERSION(1, 40, 0))
 #define NM_VERSION_1_42   (NM_ENCODE_VERSION(1, 42, 0))
-#define NM_VERSION_1_42_2 (NM_ENCODE_VERSION(1, 42, 2))
-#define NM_VERSION_1_42_8 (NM_ENCODE_VERSION(1, 42, 8))
+#define NM_VERSION_1_44   (NM_ENCODE_VERSION(1, 44, 0))
 
 /* For releases, NM_API_VERSION is equal to NM_VERSION.
  *
diff --git a/src/libnm-core-public/nm-version-macros.h.in b/src/libnm-core-public/nm-version-macros.h.in
index 711b9e19..f44199a0 100644
--- a/src/libnm-core-public/nm-version-macros.h.in
+++ b/src/libnm-core-public/nm-version-macros.h.in
@@ -72,8 +72,7 @@
 #define NM_VERSION_1_38   (NM_ENCODE_VERSION(1, 38, 0))
 #define NM_VERSION_1_40   (NM_ENCODE_VERSION(1, 40, 0))
 #define NM_VERSION_1_42   (NM_ENCODE_VERSION(1, 42, 0))
-#define NM_VERSION_1_42_2 (NM_ENCODE_VERSION(1, 42, 2))
-#define NM_VERSION_1_42_8 (NM_ENCODE_VERSION(1, 42, 8))
+#define NM_VERSION_1_44   (NM_ENCODE_VERSION(1, 44, 0))
 
 /* 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 b963671c..271535d2 100644
--- a/src/libnm-core-public/nm-version.h
+++ b/src/libnm-core-public/nm-version.h
@@ -355,16 +355,18 @@
 #define NM_AVAILABLE_IN_1_42
 #endif
 
-#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_42_2
-#define NM_AVAILABLE_IN_1_42_2 G_UNAVAILABLE(1, 42.2)
+#if NM_VERSION_MIN_REQUIRED >= NM_VERSION_1_44
+#define NM_DEPRECATED_IN_1_44        G_DEPRECATED
+#define NM_DEPRECATED_IN_1_44_FOR(f) G_DEPRECATED_FOR(f)
 #else
-#define NM_AVAILABLE_IN_1_42_2
+#define NM_DEPRECATED_IN_1_44
+#define NM_DEPRECATED_IN_1_44_FOR(f)
 #endif
 
-#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_42_8
-#define NM_AVAILABLE_IN_1_42_8 G_UNAVAILABLE(1, 42.8)
+#if NM_VERSION_MAX_ALLOWED < NM_VERSION_1_44
+#define NM_AVAILABLE_IN_1_44 G_UNAVAILABLE(1, 44)
 #else
-#define NM_AVAILABLE_IN_1_42_8
+#define NM_AVAILABLE_IN_1_44
 #endif
 
 /*
@@ -391,7 +393,8 @@
  * complaining in 1.24. That's intentional, because in 1.22 the asynchronous
  * alternative was not yet available.
  */
-#define _NM_DEPRECATED_SYNC_METHOD            NM_DEPRECATED_IN_1_24
-#define _NM_DEPRECATED_SYNC_WRITABLE_PROPERTY /* NM_DEPRECATED_IN_1_22 */
+#define _NM_DEPRECATED_SYNC_METHOD             NM_DEPRECATED_IN_1_24
+#define _NM_DEPRECATED_SYNC_WRITABLE_PROPERTY  /* NM_DEPRECATED_IN_1_22 */
+#define _NM_DEPRECATED_INCLUSIVE_LANGUAGE_1_44 /* NM_DEPRECATED_IN_1_44 */
 
 #endif /* NM_VERSION_H */
diff --git a/src/libnm-crypto/nm-crypto.c b/src/libnm-crypto/nm-crypto.c
index 8cf66336..7fcff4a9 100644
--- a/src/libnm-crypto/nm-crypto.c
+++ b/src/libnm-crypto/nm-crypto.c
@@ -899,9 +899,9 @@ nm_crypto_randomize(void *buffer, gsize buffer_len, GError **error)
  * nmtst_crypto_rsa_key_encrypt:
  * @data: (array length=len): RSA private key data to be encrypted
  * @len: length of @data
- * @in_password: (allow-none): existing password to use, if any
- * @out_password: (out) (allow-none): if @in_password was %NULL, a random
- *  password will be generated and returned in this argument
+ * @in_password: (nullable): existing password to use, if any
+ * @out_password: (out) (optional) (nullable): if @in_password was %NULL, a
+ *  random password will be generated and returned in this argument
  * @error: detailed error information on return, if an error occurred
  *
  * Encrypts the given RSA private key data with the given password (or generates
diff --git a/src/libnm-glib-aux/nm-dbus-aux.c b/src/libnm-glib-aux/nm-dbus-aux.c
index 3925da55..5c4dbc49 100644
--- a/src/libnm-glib-aux/nm-dbus-aux.c
+++ b/src/libnm-glib-aux/nm-dbus-aux.c
@@ -454,8 +454,8 @@ _bus_get_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 
 /**
  * nm_g_bus_get_blocking:
- * @cancellable: (allow-none): a #GCancellable to abort the operation.
- * @error: (allow-none): the error.
+ * @cancellable: a #GCancellable to abort the operation.
+ * @error: the error.
  *
  * This calls g_bus_get(), but iterates the current (thread-default) GMainContext
  * until the response is ready. As such, it's similar to g_bus_get_sync(),
@@ -605,10 +605,10 @@ dbus_signal_meta_marshal(GClosure     *closure,
  * _nm_dbus_proxy_signal_connect_data:
  * @proxy: a #GDBusProxy
  * @signal_name: the D-Bus signal to connect to
- * @signature: (allow-none): the signal's type signature (must be a tuple)
+ * @signature: (nullable): the signal's type signature (must be a tuple)
  * @c_handler: the signal handler function
- * @data: (allow-none): data to pass to @c_handler
- * @destroy_data: (allow-none): closure destroy notify for @data
+ * @data: (nullable): data to pass to @c_handler
+ * @destroy_data: (nullable): closure destroy notify for @data
  * @connect_flags: connection flags
  *
  * Connects to the D-Bus signal @signal_name on @proxy. @c_handler must be a
@@ -690,7 +690,7 @@ _nm_dbus_typecheck_response(GVariant *response, const GVariantType *reply_type,
  * @proxy: A #GDBusProxy.
  * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to
  *   g_dbus_proxy_call().
- * @reply_type: (allow-none): the expected type of the reply, or %NULL
+ * @reply_type: (nullable): the expected type of the reply, or %NULL
  * @error: Return location for error or %NULL.
  *
  * Finishes an operation started with g_dbus_proxy_call(), as with
diff --git a/src/libnm-glib-aux/nm-dbus-aux.h b/src/libnm-glib-aux/nm-dbus-aux.h
index 5db79c08..5bbb01e9 100644
--- a/src/libnm-glib-aux/nm-dbus-aux.h
+++ b/src/libnm-glib-aux/nm-dbus-aux.h
@@ -290,7 +290,7 @@ gulong _nm_dbus_proxy_signal_connect_data(GDBusProxy         *proxy,
  * @signal_name: the D-Bus signal to connect to
  * @signature: the signal's type signature (must be a tuple)
  * @c_handler: the signal handler function
- * @data: (allow-none): data to pass to @c_handler
+ * @data: (nullable): data to pass to @c_handler
  *
  * Simplified version of _nm_dbus_proxy_signal_connect_data() with fewer arguments.
  *
diff --git a/src/libnm-glib-aux/nm-dedup-multi.c b/src/libnm-glib-aux/nm-dedup-multi.c
index 625c4ef3..cf2dba90 100644
--- a/src/libnm-glib-aux/nm-dedup-multi.c
+++ b/src/libnm-glib-aux/nm-dedup-multi.c
@@ -97,7 +97,7 @@ _entry_lookup_head(const NMDedupMultiIndex   *self,
         if (c_list_is_empty(&idx_type->lst_idx_head))
             head_entry = NULL;
         else {
-            nm_assert(c_list_length(&idx_type->lst_idx_head) == 1);
+            nm_assert(c_list_length_is(&idx_type->lst_idx_head, 1));
             head_entry = c_list_entry(idx_type->lst_idx_head.next, NMDedupMultiHeadEntry, lst_idx);
         }
         nm_assert(head_entry == g_hash_table_lookup(self->idx_entries, &stack_entry));
@@ -420,7 +420,7 @@ nm_dedup_multi_index_add(NMDedupMultiIndex                         *self,
  *
  * Adds and object to the index.
  *
- * Return: %TRUE if anything changed, %FALSE if nothing changed.
+ * Returns: %TRUE if anything changed, %FALSE if nothing changed.
  */
 gboolean
 nm_dedup_multi_index_add_full(NMDedupMultiIndex                         *self,
@@ -951,7 +951,7 @@ nm_dedup_multi_objs_to_ptr_array_head(const NMDedupMultiHeadEntry   *head_entry,
 /**
  * nm_dedup_multi_entry_reorder:
  * @entry: the entry to reorder. It must not be NULL (and tracked in an index).
- * @entry_order: (allow-none): an optional other entry. It MUST be in the same
+ * @entry_order: (nullable): an optional other entry. It MUST be in the same
  *   list as entry. If given, @entry will be ordered after/before @entry_order.
  *   If left at %NULL, @entry will be moved to the front/end of the list.
  * @order_after: if @entry_order is given, %TRUE means to move @entry after
diff --git a/src/libnm-glib-aux/nm-glib.h b/src/libnm-glib-aux/nm-glib.h
index 0436d135..9c90d429 100644
--- a/src/libnm-glib-aux/nm-glib.h
+++ b/src/libnm-glib-aux/nm-glib.h
@@ -500,7 +500,7 @@ _nm_g_variant_new_take_string(char *string)
     return value;
 #elif !GLIB_CHECK_VERSION(2, 38, 0)
     GVariant *value;
-    GBytes *bytes;
+    GBytes   *bytes;
 
     g_return_val_if_fail(string != NULL, NULL);
     g_return_val_if_fail(g_utf8_validate(string, -1, NULL), NULL);
diff --git a/src/libnm-glib-aux/nm-hash-utils.h b/src/libnm-glib-aux/nm-hash-utils.h
index e2707fc2..c1306200 100644
--- a/src/libnm-glib-aux/nm-hash-utils.h
+++ b/src/libnm-glib-aux/nm-hash-utils.h
@@ -185,10 +185,10 @@ nm_hash_update_str(NMHashState *state, const char *str)
 /* Like nm_hash_update_str(), but restricted to arrays only. nm_hash_update_str() only works
  * with a @str argument that cannot be NULL. If you have a string pointer, that is never NULL, use
  * nm_hash_update() instead. */
-#define nm_hash_update_strarr(state, str)                                \
-    (_Generic(&(str), const char(*)[sizeof(str)]                         \
-              : nm_hash_update_str((state), (str)), char(*)[sizeof(str)] \
-              : nm_hash_update_str((state), (str))))
+#define nm_hash_update_strarr(state, str)                               \
+    (_Generic(&(str),                                                   \
+        const char(*)[sizeof(str)]: nm_hash_update_str((state), (str)), \
+        char(*)[sizeof(str)]: nm_hash_update_str((state), (str))))
 #else
 #define nm_hash_update_strarr(state, str) nm_hash_update_str((state), (str))
 #endif
diff --git a/src/libnm-glib-aux/nm-inet-utils.c b/src/libnm-glib-aux/nm-inet-utils.c
index 7f710f35..2ee73ad5 100644
--- a/src/libnm-glib-aux/nm-inet-utils.c
+++ b/src/libnm-glib-aux/nm-inet-utils.c
@@ -6,6 +6,7 @@
 
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <dlfcn.h>
 
 /*****************************************************************************/
 
@@ -266,34 +267,118 @@ nm_ip6_addr_same_prefix_cmp(const struct in6_addr *addr_a,
 
 /*****************************************************************************/
 
-static gboolean
-_parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error)
+static int
+_inet_aton(const char *text, in_addr_t *out_addr)
 {
-    gs_free char  *s_free = NULL;
-    struct in_addr a1;
-    guint8         bin[sizeof(a1)];
-    char          *s;
-    int            i;
+    /* Call inet_aton() via dlopen.
+     *
+     * The inet_aton() API is discouraged, and ABI checkers warn when we call
+     * it.
+     *
+     * We want to use this function, but only for testing/asserting. To avoid
+     * the ABI checker's complain, dlopen() the symbol. This is not used for
+     * production.
+     */
+    static gpointer fcn_sym     = NULL;
+    static gsize    initialized = 0;
+    int (*fcn)(const char *text, struct in_addr *out_addr);
+    int       r;
+    in_addr_t a;
+
+    if (g_once_init_enter(&initialized)) {
+        GModule *module;
+
+        module = g_module_open(NULL, G_MODULE_BIND_LAZY);
+        if (module) {
+            if (!g_module_symbol(module, "inet_aton", &fcn_sym))
+                g_module_close(module);
+            else
+                g_module_make_resident(module);
+        }
+        g_once_init_leave(&initialized, 1);
+    }
 
-    if (inet_aton(text, &a1) != 1) {
-        g_set_error_literal(error,
-                            NM_UTILS_ERROR,
-                            NM_UTILS_ERROR_INVALID_ARGUMENT,
-                            "address invalid according to inet_aton()");
-        return FALSE;
+    if (!fcn_sym)
+        return -ENOSYS;
+
+    fcn = fcn_sym;
+    r   = fcn(text, (gpointer) &a);
+
+    if (r != 1)
+        return -EINVAL;
+
+    NM_SET_OUT(out_addr, a);
+    return 0;
+}
+
+int
+nmtst_inet_aton(const char *text, in_addr_t *out_addr)
+{
+    return _inet_aton(text, out_addr);
+}
+
+static void
+_nm_assert_legacy_addr4(const char *text, in_addr_t addr)
+{
+#if NM_MORE_ASSERTS > 20
+    char      buf1[NM_INET_ADDRSTRLEN];
+    char      buf2[NM_INET_ADDRSTRLEN];
+    int       r;
+    in_addr_t a;
+
+    /* Our legacy parser accepted "text" as "addr".
+     *
+     * However, we want to ensure that whatever we parse is also parsed by old
+     * inet_aton(). So we want to be strictly more strict than inet_aton() in
+     * what we accept.
+     */
+
+    r = _inet_aton(text, &a);
+
+    if (r != 0) {
+        if (r == -ENOSYS)
+            return;
+        g_error("inet_aton(\"%s\") failed with \"%s\", but we expected %s",
+                text,
+                nm_strerror_native(-r),
+                nm_inet4_ntop(addr, buf2));
     }
 
-    /* OK, inet_aton() accepted the format. That's good, because we want
-     * to accept IPv4 addresses in octal format, like 255.255.000.000.
-     * That's what "legacy" means here. inet_pton() doesn't accept those.
+    if (a != addr) {
+        g_error("inet_aton(\"%s\") parsed %s, but we expected %s",
+                text,
+                nm_inet4_ntop(a, buf1),
+                nm_inet4_ntop(addr, buf2));
+    }
+#endif
+}
+
+static gboolean
+_parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error)
+{
+    gs_free char *s_free = NULL;
+    union {
+        guint8    b[sizeof(in_addr_t)];
+        in_addr_t a;
+    } addr;
+    char *s;
+    int   i;
+
+    /* inet_pton() does strict parsing of IPv4 address. Good.
      *
-     * But inet_aton() also ignores trailing garbage and formats with fewer than
-     * 4 digits. That is just too crazy and we don't do that. Perform additional checks
-     * and reject some forms that inet_aton() accepted.
+     * However, inet_aton() used to accept much more relaxed forms (e.g. octal
+     * and hex numbers, not having 4 components but fewer, ignore any trailing
+     * garbage).
+     *
+     * Some places where we accept input, we want to be slightly more forgiving
+     * than inet_pton() and accept some (not all!) forms of what inet_aton()
+     * would accept. For example, we want to accept 255.000.000.000.
+     *
+     * We reimplement that below.
      *
      * Note that we still should (of course) accept everything that inet_pton()
-     * accepts. However this code never gets called if inet_pton() succeeds
-     * (see below, aside the assertion code). */
+     * accepts. This is ensured because the caller only calls this function
+     * after inet_pton() failed. */
 
     if (NM_STRCHAR_ANY(text, ch, (!(ch >= '0' && ch <= '9') && !NM_IN_SET(ch, '.', 'x')))) {
         /* We only accepts '.', digits, and 'x' for "0x". */
@@ -306,7 +391,7 @@ _parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error)
 
     s = nm_memdup_maybe_a(300, text, strlen(text) + 1, &s_free);
 
-    for (i = 0; i < G_N_ELEMENTS(bin); i++) {
+    for (i = 0; i < G_N_ELEMENTS(addr.b); i++) {
         char  *current_token = s;
         gint32 v;
 
@@ -316,7 +401,7 @@ _parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error)
             s++;
         }
 
-        if ((i == G_N_ELEMENTS(bin) - 1) != (s == NULL)) {
+        if ((i == G_N_ELEMENTS(addr.b) - 1) != (s == NULL)) {
             /* Exactly for the last digit, we expect to have no more following token.
              * But this isn't the case. Abort. */
             g_set_error(error,
@@ -344,26 +429,12 @@ _parse_legacy_addr4(const char *text, in_addr_t *out_addr, GError **error)
             return FALSE;
         }
 
-        bin[i] = v;
+        addr.b[i] = v;
     }
 
-    if (memcmp(bin, &a1, sizeof(bin)) != 0) {
-        /* our parsing did not agree with what inet_aton() gave. Something
-         * is wrong. Abort. */
-        g_set_error(
-            error,
-            NM_UTILS_ERROR,
-            NM_UTILS_ERROR_INVALID_ARGUMENT,
-            "inet_aton() result 0x%08x differs from computed value 0x%02hhx%02hhx%02hhx%02hhx",
-            a1.s_addr,
-            bin[0],
-            bin[1],
-            bin[2],
-            bin[3]);
-        return FALSE;
-    }
+    _nm_assert_legacy_addr4(text, addr.a);
 
-    *out_addr = a1.s_addr;
+    *out_addr = addr.a;
     return TRUE;
 }
 
diff --git a/src/libnm-glib-aux/nm-inet-utils.h b/src/libnm-glib-aux/nm-inet-utils.h
index 8421929e..65ceeb2e 100644
--- a/src/libnm-glib-aux/nm-inet-utils.h
+++ b/src/libnm-glib-aux/nm-inet-utils.h
@@ -3,26 +3,28 @@
 #ifndef __NM_INET_UTILS_H__
 #define __NM_INET_UTILS_H__
 
-typedef struct _NMIPAddr {
-    union {
-        guint8          addr_ptr[sizeof(struct in6_addr)];
-        in_addr_t       addr4;
-        struct in_addr  addr4_struct;
-        struct in6_addr addr6;
-
-        /* NMIPAddr is really a union for IP addresses.
-         * However, as ethernet addresses fit in here nicely, use
-         * it also for an ethernet MAC address. */
-        guint8      ether_addr_octet[6 /*ETH_ALEN*/];
-        NMEtherAddr ether_addr;
-
-        guint8 array[sizeof(struct in6_addr)];
-    };
+#include "libnm-std-aux/unaligned-fundamental.h"
+
+typedef union _NMIPAddr {
+    guint8          addr_ptr[sizeof(struct in6_addr)];
+    in_addr_t       addr4;
+    struct in_addr  addr4_struct;
+    struct in6_addr addr6;
+
+    /* This union field only exists, so that it's guaranteed that NMIPAddr has
+     * a suitable alignment. We use that with nm_ether_addr_zero macro, that
+     * aliases nm_ip_addr_zero. */
+    NMEtherAddr _ether_addr;
 } NMIPAddr;
 
-#define NM_IP_ADDR_INIT \
-    {                   \
-        .array = { 0 }  \
+typedef struct _NMIPAddrTyped {
+    NMIPAddr addr;
+    gint8    addr_family;
+} NMIPAddrTyped;
+
+#define NM_IP_ADDR_INIT   \
+    {                     \
+        .addr_ptr = { 0 } \
     }
 
 #define _NM_IN6ADDR_INIT(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, aa, ab, ac, ad, ae, af) \
@@ -90,14 +92,15 @@ nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src)
 static inline gboolean
 nm_ip_addr_is_null(int addr_family, gconstpointer addr)
 {
-    NMIPAddr a;
+    struct in6_addr a6;
 
-    nm_ip_addr_set(addr_family, &a, addr);
+    nm_assert(addr);
 
     if (NM_IS_IPv4(addr_family))
-        return a.addr4 == 0;
+        return unaligned_read_ne32(addr) == 0;
 
-    return IN6_IS_ADDR_UNSPECIFIED(&a.addr6);
+    memcpy(&a6, addr, sizeof(struct in6_addr));
+    return IN6_IS_ADDR_UNSPECIFIED(&a6);
 }
 
 static inline NMIPAddr
@@ -117,7 +120,7 @@ nm_ip_addr_init(int addr_family, gconstpointer src)
 
         /* ensure all bytes of the union are initialized. If only to make
          * valgrind happy. */
-        memset(&a.array[sizeof(in_addr_t)], 0, sizeof(a) - sizeof(in_addr_t));
+        memset(&a.addr_ptr[sizeof(in_addr_t)], 0, sizeof(a) - sizeof(in_addr_t));
     } else
         memcpy(&a, src, sizeof(struct in6_addr));
 
@@ -143,6 +146,30 @@ nm_ip_addr_from_packed_array(int addr_family, gconstpointer ipaddr_arr, gsize id
 
 /*****************************************************************************/
 
+static inline int
+nm_ip_addr_typed_cmp(const NMIPAddrTyped *a, const NMIPAddrTyped *b)
+{
+    NM_CMP_SELF(a, b);
+    NM_CMP_FIELD(a, b, addr_family);
+    NM_CMP_DIRECT_MEMCMP(&a->addr, &b->addr, nm_utils_addr_family_to_size(a->addr_family));
+    return 0;
+}
+
+static inline gboolean
+nm_ip_addr_typed_equal(const NMIPAddrTyped *a, const NMIPAddrTyped *b)
+{
+    return nm_ip_addr_typed_cmp(a, b) == 0;
+}
+
+static inline void
+nm_ip_addr_typed_hash_update(NMHashState *h, const NMIPAddrTyped *addr)
+{
+    nm_hash_update_vals(h, addr->addr_family);
+    nm_hash_update_mem(h, &addr->addr, nm_utils_addr_family_to_size(addr->addr_family));
+}
+
+/*****************************************************************************/
+
 static inline guint32
 nm_ip4_addr_netmask_to_prefix(in_addr_t subnetmask)
 {
@@ -342,6 +369,8 @@ nm_inet6_ntop_dup(const struct in6_addr *addr)
 
 /*****************************************************************************/
 
+int nmtst_inet_aton(const char *text, in_addr_t *out_addr);
+
 gboolean nm_inet_parse_bin_full(int         addr_family,
                                 gboolean    accept_legacy,
                                 const char *text,
diff --git a/src/libnm-glib-aux/nm-io-utils.c b/src/libnm-glib-aux/nm-io-utils.c
index d1430595..ec016ed8 100644
--- a/src/libnm-glib-aux/nm-io-utils.c
+++ b/src/libnm-glib-aux/nm-io-utils.c
@@ -21,6 +21,86 @@
 
 /*****************************************************************************/
 
+int
+nm_io_fcntl_getfl(int fd)
+{
+    int f;
+
+    nm_assert(fd >= 0);
+
+    f = fcntl(fd, F_GETFL, 0);
+
+    /* The caller really must provide a valid FD. For a valid FD, there is not
+     * reason why this call could fail (or how we could handle the failure).
+     *
+     * Unlike plain fcntl(), nm_io_fcntl_getfl() cannot fail. */
+    nm_assert(f != -1);
+
+    /* We not only assert that the return value is "!= -1", but that it's not
+     * negative. Negative flags would be very odd, and not something we would
+     * expect for a successful call. */
+    nm_assert(f >= 0);
+
+    return f;
+}
+
+int
+nm_io_fcntl_setfl(int fd, int flags)
+{
+    int f;
+    int errsv;
+
+    nm_assert(fd >= 0);
+    nm_assert(flags >= 0);
+
+    f = fcntl(fd, F_SETFL, flags);
+    if (f != 0) {
+        errsv = errno;
+
+        nm_assert(errsv != EBADF);
+
+        return -NM_ERRNO_NATIVE(errsv);
+    }
+
+    return 0;
+}
+
+int
+nm_io_fcntl_setfl_update(int fd, int flags_mask, int flags_value)
+{
+    int flags_current;
+
+    nm_assert(fd >= 0);
+    nm_assert(flags_mask > 0);
+    nm_assert(flags_value >= 0);
+    nm_assert(((~flags_mask) & flags_value) == 0);
+
+    flags_current = nm_io_fcntl_getfl(fd);
+    return nm_io_fcntl_setfl(fd, (flags_current & ~flags_mask) | (flags_mask & flags_value));
+}
+
+void
+nm_io_fcntl_setfl_update_nonblock(int fd)
+{
+    int r;
+
+    nm_assert(fd >= 0);
+
+    r = nm_io_fcntl_setfl_update(fd, O_NONBLOCK, O_NONBLOCK);
+
+    /* nm_io_fcntl_setfl_update() already asserts that it cannot fail with
+     * EBADF.
+     *
+     * In nm_io_fcntl_setfl_update_nonblock() only sts O_NONBLOCK, where we
+     * don't expect any other error. Kernel should never reject setting this
+     * flags, and if it did, we have to find out how to handle that. Currently
+     * we don't handle it and assert against failure. */
+
+    nm_assert(r == 0);
+}
+
+/*****************************************************************************/
+
 _nm_printf(4, 5) static int _get_contents_error(GError    **error,
                                                 int         errsv,
                                                 int        *out_errsv,
@@ -79,7 +159,7 @@ _nm_printf(4, 5) static int _get_contents_error(GError    **error,
  *  the NUL byte. That is, it reads only files up to a length of
  *  @max_length - 1 bytes.
  * @length: optional output argument of the read file size.
- * @out_errsv: (allow-none) (out): on error, a positive errno. or zero.
+ * @out_errsv: (out) (optional): on error, a positive errno. or zero.
  * @error:
  *
  *
@@ -276,7 +356,7 @@ nm_utils_fd_get_contents(int                         fd,
  *   the NUL byte. That is, it reads only files up to a length of
  *   @max_length - 1 bytes.
  * @length: optional output argument of the read file size.
- * @out_errsv: (allow-none) (out): on error, a positive errno. or zero.
+ * @out_errsv: (out) (optional): on error, a positive errno. or zero.
  * @error:
  *
  * A reimplementation of g_file_get_contents() with a few differences:
@@ -444,7 +524,7 @@ nm_utils_file_set_contents(const char            *filename,
 /**
  * nm_utils_file_stat:
  * @filename: the filename to stat.
- * @out_st: (allow-none) (out): if given, this will be passed to stat().
+ * @out_st: (out) (nullable): if given, this will be passed to stat().
  *
  * Just wraps stat() and gives the errno number as function result instead
  * of setting the errno (though, errno is also set). It's only for convenience
@@ -482,13 +562,17 @@ nm_utils_fd_read(int fd, NMStrBuf *out_string)
     g_return_val_if_fail(fd >= 0, -1);
     g_return_val_if_fail(out_string, -1);
 
-    /* If the buffer size is 0, we allocate NM_UTILS_GET_NEXT_REALLOC_SIZE_1000 (1000 bytes)
-     * the first time. Afterwards, the buffer grows exponentially.
+    /* Reserve at least 488+1 bytes of buffer size. That is probably a suitable
+     * compromise between not wasting too much buffer space and not reading too much.
      *
-     * Note that with @buf_available, we always would read as much buffer as we actually
-     * have reserved. */
-    nm_str_buf_maybe_expand(out_string, NM_UTILS_GET_NEXT_REALLOC_SIZE_1000, FALSE);
+     * Note that when we start with an empty buffer, the first allocation of
+     * 488+1 bytes will actually allocate 1000 bytes. So if we were to receive
+     * one byte at a time, we don't need a reallocation for the first 1000-(488+1)
+     * bytes. Afterwards grows the buffer exponentially.
+     */
+    nm_str_buf_maybe_expand(out_string, NM_UTILS_GET_NEXT_REALLOC_SIZE_488 + 1, FALSE);
 
+    /* We always use all the available buffer size. */
     buf_available = out_string->allocated - out_string->len;
 
     n_read = read(fd, &((nm_str_buf_get_str_unsafe(out_string))[out_string->len]), buf_available);
diff --git a/src/libnm-glib-aux/nm-io-utils.h b/src/libnm-glib-aux/nm-io-utils.h
index 54018140..0021138f 100644
--- a/src/libnm-glib-aux/nm-io-utils.h
+++ b/src/libnm-glib-aux/nm-io-utils.h
@@ -10,6 +10,16 @@
 
 /*****************************************************************************/
 
+int nm_io_fcntl_getfl(int fd);
+
+int nm_io_fcntl_setfl(int fd, int flags);
+
+int nm_io_fcntl_setfl_update(int fd, int flags_mask, int flags_value);
+
+void nm_io_fcntl_setfl_update_nonblock(int fd);
+
+/*****************************************************************************/
+
 /**
  * NMUtilsFileGetContentsFlags:
  * @NM_UTILS_FILE_GET_CONTENTS_FLAG_NONE: no flag
diff --git a/src/libnm-glib-aux/nm-logging-fwd.h b/src/libnm-glib-aux/nm-logging-fwd.h
index 72e5723c..f7ab9098 100644
--- a/src/libnm-glib-aux/nm-logging-fwd.h
+++ b/src/libnm-glib-aux/nm-logging-fwd.h
@@ -177,8 +177,8 @@ extern void _nm_utils_monotonic_timestamp_initialized(const struct timespec *tp,
 #define _LOGW(...) _NMLOG(_LOGL_WARN, __VA_ARGS__)
 #define _LOGE(...) _NMLOG(_LOGL_ERR, __VA_ARGS__)
 
-#define _LOGT_ENABLED(...) _NMLOG_ENABLED(_LOGL_TRACE, ##__VA_ARGS__)
-#define _LOGD_ENABLED(...) _NMLOG_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__)
+#define _LOGT_ENABLED(...) G_UNLIKELY(_NMLOG_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))
+#define _LOGD_ENABLED(...) G_UNLIKELY(_NMLOG_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__))
 #define _LOGI_ENABLED(...) _NMLOG_ENABLED(_LOGL_INFO, ##__VA_ARGS__)
 #define _LOGW_ENABLED(...) _NMLOG_ENABLED(_LOGL_WARN, ##__VA_ARGS__)
 #define _LOGE_ENABLED(...) _NMLOG_ENABLED(_LOGL_ERR, ##__VA_ARGS__)
@@ -192,7 +192,7 @@ extern void _nm_utils_monotonic_timestamp_initialized(const struct timespec *tp,
 /* _LOGT() and _LOGt() both log with level TRACE, but the latter is disabled by default,
  * unless building with --with-more-logging. */
 #if NM_MORE_LOGGING
-#define _LOGt_ENABLED(...)    _NMLOG_ENABLED(_LOGL_TRACE, ##__VA_ARGS__)
+#define _LOGt_ENABLED(...)    G_UNLIKELY(_NMLOG_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))
 #define _LOGt(...)            _NMLOG(_LOGL_TRACE, __VA_ARGS__)
 #define _LOGt_err(errsv, ...) _NMLOG_err(errsv, _LOGL_TRACE, __VA_ARGS__)
 #else
@@ -233,8 +233,8 @@ extern void _nm_utils_monotonic_timestamp_initialized(const struct timespec *tp,
 #define _LOG2W(...) _NMLOG2(_LOGL_WARN, __VA_ARGS__)
 #define _LOG2E(...) _NMLOG2(_LOGL_ERR, __VA_ARGS__)
 
-#define _LOG2T_ENABLED(...) _NMLOG2_ENABLED(_LOGL_TRACE, ##__VA_ARGS__)
-#define _LOG2D_ENABLED(...) _NMLOG2_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__)
+#define _LOG2T_ENABLED(...) G_UNLIKELY(_NMLOG2_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))
+#define _LOG2D_ENABLED(...) G_UNLIKELY(_NMLOG2_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__))
 #define _LOG2I_ENABLED(...) _NMLOG2_ENABLED(_LOGL_INFO, ##__VA_ARGS__)
 #define _LOG2W_ENABLED(...) _NMLOG2_ENABLED(_LOGL_WARN, ##__VA_ARGS__)
 #define _LOG2E_ENABLED(...) _NMLOG2_ENABLED(_LOGL_ERR, ##__VA_ARGS__)
@@ -246,7 +246,7 @@ extern void _nm_utils_monotonic_timestamp_initialized(const struct timespec *tp,
 #define _LOG2E_err(errsv, ...) _NMLOG2_err(errsv, _LOGL_ERR, __VA_ARGS__)
 
 #if NM_MORE_LOGGING
-#define _LOG2t_ENABLED(...)    _NMLOG2_ENABLED(_LOGL_TRACE, ##__VA_ARGS__)
+#define _LOG2t_ENABLED(...)    G_UNLIKELY(_NMLOG2_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))
 #define _LOG2t(...)            _NMLOG2(_LOGL_TRACE, __VA_ARGS__)
 #define _LOG2t_err(errsv, ...) _NMLOG2_err(errsv, _LOGL_TRACE, __VA_ARGS__)
 #else
@@ -278,8 +278,8 @@ extern void _nm_utils_monotonic_timestamp_initialized(const struct timespec *tp,
 #define _LOG3W(...) _NMLOG3(_LOGL_WARN, __VA_ARGS__)
 #define _LOG3E(...) _NMLOG3(_LOGL_ERR, __VA_ARGS__)
 
-#define _LOG3T_ENABLED(...) _NMLOG3_ENABLED(_LOGL_TRACE, ##__VA_ARGS__)
-#define _LOG3D_ENABLED(...) _NMLOG3_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__)
+#define _LOG3T_ENABLED(...) G_UNLIKELY(_NMLOG3_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))
+#define _LOG3D_ENABLED(...) G_UNLIKELY(_NMLOG3_ENABLED(_LOGL_DEBUG, ##__VA_ARGS__))
 #define _LOG3I_ENABLED(...) _NMLOG3_ENABLED(_LOGL_INFO, ##__VA_ARGS__)
 #define _LOG3W_ENABLED(...) _NMLOG3_ENABLED(_LOGL_WARN, ##__VA_ARGS__)
 #define _LOG3E_ENABLED(...) _NMLOG3_ENABLED(_LOGL_ERR, ##__VA_ARGS__)
@@ -291,7 +291,7 @@ extern void _nm_utils_monotonic_timestamp_initialized(const struct timespec *tp,
 #define _LOG3E_err(errsv, ...) _NMLOG3_err(errsv, _LOGL_ERR, __VA_ARGS__)
 
 #if NM_MORE_LOGGING
-#define _LOG3t_ENABLED(...)    _NMLOG3_ENABLED(_LOGL_TRACE, ##__VA_ARGS__)
+#define _LOG3t_ENABLED(...)    G_UNLIKELY(_NMLOG3_ENABLED(_LOGL_TRACE, ##__VA_ARGS__))
 #define _LOG3t(...)            _NMLOG3(_LOGL_TRACE, __VA_ARGS__)
 #define _LOG3t_err(errsv, ...) _NMLOG3_err(errsv, _LOGL_TRACE, __VA_ARGS__)
 #else
diff --git a/src/libnm-glib-aux/nm-macros-internal.h b/src/libnm-glib-aux/nm-macros-internal.h
index 0534ee5d..9972dc44 100644
--- a/src/libnm-glib-aux/nm-macros-internal.h
+++ b/src/libnm-glib-aux/nm-macros-internal.h
@@ -239,7 +239,7 @@ _nm_auto_freev(gpointer ptr)
 
 /**
  * NM_G_ERROR_MSG:
- * @error: (allow-none): the #GError instance
+ * @error: (nullable): the #GError instance
  *
  * All functions must follow the convention that when they
  * return a failure, they must also set the GError to a valid
@@ -259,41 +259,41 @@ NM_G_ERROR_MSG(GError *error)
 
 #if _NM_CC_SUPPORT_GENERIC
 #define _NM_CONSTCAST_FULL_1(type, obj_expr, obj) \
-    (_Generic ((obj_expr), \
-               const void        *: ((const type *) (obj)), \
-                     void        *: ((      type *) (obj)), \
-               const type        *: ((const type *) (obj)), \
-                     type        *: ((      type *) (obj))))
+    (_Generic((obj_expr),                         \
+        const void *: ((const type *) (obj)),     \
+        void *: ((type *) (obj)),                 \
+        const type *: ((const type *) (obj)),     \
+        type *: ((type *) (obj))))
 #define _NM_CONSTCAST_FULL_2(type, obj_expr, obj, alias_type2) \
-    (_Generic ((obj_expr), \
-               const void        *: ((const type *) (obj)), \
-                     void        *: ((      type *) (obj)), \
-               const alias_type2 *: ((const type *) (obj)), \
-                     alias_type2 *: ((      type *) (obj)), \
-               const type        *: ((const type *) (obj)), \
-                     type        *: ((      type *) (obj))))
+    (_Generic((obj_expr),                                      \
+        const void *: ((const type *) (obj)),                  \
+        void *: ((type *) (obj)),                              \
+        const alias_type2 *: ((const type *) (obj)),           \
+        alias_type2 *: ((type *) (obj)),                       \
+        const type *: ((const type *) (obj)),                  \
+        type *: ((type *) (obj))))
 #define _NM_CONSTCAST_FULL_3(type, obj_expr, obj, alias_type2, alias_type3) \
-    (_Generic ((obj_expr), \
-               const void        *: ((const type *) (obj)), \
-                     void        *: ((      type *) (obj)), \
-               const alias_type2 *: ((const type *) (obj)), \
-                     alias_type2 *: ((      type *) (obj)), \
-               const alias_type3 *: ((const type *) (obj)), \
-                     alias_type3 *: ((      type *) (obj)), \
-               const type        *: ((const type *) (obj)), \
-                     type        *: ((      type *) (obj))))
+    (_Generic((obj_expr),                                                   \
+        const void *: ((const type *) (obj)),                               \
+        void *: ((type *) (obj)),                                           \
+        const alias_type2 *: ((const type *) (obj)),                        \
+        alias_type2 *: ((type *) (obj)),                                    \
+        const alias_type3 *: ((const type *) (obj)),                        \
+        alias_type3 *: ((type *) (obj)),                                    \
+        const type *: ((const type *) (obj)),                               \
+        type *: ((type *) (obj))))
 #define _NM_CONSTCAST_FULL_4(type, obj_expr, obj, alias_type2, alias_type3, alias_type4) \
-    (_Generic ((obj_expr), \
-               const void        *: ((const type *) (obj)), \
-                     void        *: ((      type *) (obj)), \
-               const alias_type2 *: ((const type *) (obj)), \
-                     alias_type2 *: ((      type *) (obj)), \
-               const alias_type3 *: ((const type *) (obj)), \
-                     alias_type3 *: ((      type *) (obj)), \
-               const alias_type4 *: ((const type *) (obj)), \
-                     alias_type4 *: ((      type *) (obj)), \
-               const type        *: ((const type *) (obj)), \
-                     type        *: ((      type *) (obj))))
+    (_Generic((obj_expr),                                                                \
+        const void *: ((const type *) (obj)),                                            \
+        void *: ((type *) (obj)),                                                        \
+        const alias_type2 *: ((const type *) (obj)),                                     \
+        alias_type2 *: ((type *) (obj)),                                                 \
+        const alias_type3 *: ((const type *) (obj)),                                     \
+        alias_type3 *: ((type *) (obj)),                                                 \
+        const alias_type4 *: ((const type *) (obj)),                                     \
+        alias_type4 *: ((type *) (obj)),                                                 \
+        const type *: ((const type *) (obj)),                                            \
+        type *: ((type *) (obj))))
 #define _NM_CONSTCAST_FULL_x(type, obj_expr, obj, n, ...) \
     (_NM_CONSTCAST_FULL_##n(type, obj_expr, obj, ##__VA_ARGS__))
 #define _NM_CONSTCAST_FULL_y(type, obj_expr, obj, n, ...) \
@@ -308,18 +308,18 @@ NM_G_ERROR_MSG(GError *error)
 
 #if _NM_CC_SUPPORT_GENERIC
 #define NM_UNCONST_PTR(type, arg) \
-    _Generic((arg), const type * : ((type *) (arg)), type * : ((type *) (arg)))
+    _Generic((arg), const type *: ((type *) (arg)), type *: ((type *) (arg)))
 #else
 #define NM_UNCONST_PTR(type, arg) ((type *) (arg))
 #endif
 
 #if _NM_CC_SUPPORT_GENERIC
-#define NM_UNCONST_PPTR(type, arg) \
-    _Generic ((arg), \
-              const type *     *: ((type **) (arg)), \
-                    type *     *: ((type **) (arg)), \
-              const type *const*: ((type **) (arg)), \
-                    type *const*: ((type **) (arg)))
+#define NM_UNCONST_PPTR(type, arg)              \
+    _Generic((arg),                             \
+        const type **: ((type **) (arg)),       \
+        type **: ((type **) (arg)),             \
+        const type *const *: ((type **) (arg)), \
+        type *const *: ((type **) (arg)))
 #else
 #define NM_UNCONST_PPTR(type, arg) ((type **) (arg))
 #endif
@@ -377,30 +377,29 @@ NM_G_ERROR_MSG(GError *error)
  * These macros do the cast, but they only accept a compatible input
  * type, otherwise they will fail compilation.
  */
-#define NM_CAST_STRV_MC(value) \
-    (_Generic ((value), \
-               const char *     *: (const char *     *) (value), \
-                     char *     *: (const char *     *) (value), \
-                           void *: (const char *     *) (value)))
-#define NM_CAST_STRV_CC(value) \
-    (_Generic ((value), \
-               const char *const*: (const char *const*) (value), \
-               const char *     *: (const char *const*) (value), \
-                     char *const*: (const char *const*) (value), \
-                     char *     *: (const char *const*) (value), \
-                     const void *: (const char *const*) (value), \
-                           void *: (const char *const*) (value)))
+#define NM_CAST_STRV_MC(value)                  \
+    (_Generic((value),                          \
+        const char **: (const char **) (value), \
+        char **: (const char **) (value),       \
+        void *: (const char **) (value)))
+#define NM_CAST_STRV_CC(value)                              \
+    (_Generic((value),                                      \
+        const char *const *: (const char *const *) (value), \
+        const char **: (const char *const *) (value),       \
+        char *const *: (const char *const *) (value),       \
+        char **: (const char *const *) (value),             \
+        const void *: (const char *const *) (value),        \
+        void *: (const char *const *) (value)))
 #else
 #define NM_CAST_STRV_MC(value) ((const char **) (value))
 #define NM_CAST_STRV_CC(value) ((const char *const *) (value))
 #endif
 
 #if _NM_CC_SUPPORT_GENERIC
-#define NM_PROPAGATE_CONST(test_expr, ptr) \
-    (_Generic ((test_expr), \
-               const typeof (*(test_expr)) *: ((const typeof (*(ptr)) *) (ptr)), \
-                                     default: (_Generic ((test_expr), \
-                                                         typeof (*(test_expr)) *: (ptr)))))
+#define NM_PROPAGATE_CONST(test_expr, ptr)                              \
+    (_Generic((test_expr),                                              \
+        const typeof(*(test_expr)) *: ((const typeof(*(ptr)) *) (ptr)), \
+        default: (_Generic((test_expr), typeof(*(test_expr)) *: (ptr)))))
 #else
 #define NM_PROPAGATE_CONST(test_expr, ptr) (ptr)
 #endif
@@ -1023,34 +1022,43 @@ nm_g_variant_equal(GVariant *a, GVariant *b)
 #define NM_FLAGS_SET(flags, val)              \
     ({                                        \
         const typeof(flags) _flags = (flags); \
-        const typeof(flags) _val   = (val);   \
+        typeof(_flags)      _val   = (val);   \
                                               \
-        _flags | _val;                        \
+        (void) (FALSE && (flags) == (val));   \
+                                              \
+        (_flags | _val);                      \
     })
 
 #define NM_FLAGS_UNSET(flags, val)            \
     ({                                        \
         const typeof(flags) _flags = (flags); \
-        const typeof(flags) _val   = (val);   \
+        typeof(_flags)      _val   = (val);   \
+                                              \
+        (void) (FALSE && (flags) == (val));   \
                                               \
-        _flags & (~_val);                     \
+        (_flags & (~_val));                   \
     })
 
-#define NM_FLAGS_ASSIGN(flags, val, assign)            \
-    ({                                                 \
-        const typeof(flags) _flags = (flags);          \
-        const typeof(flags) _val   = (val);            \
-                                                       \
-        (assign) ? _flags | (_val) : _flags & (~_val); \
+#define NM_FLAGS_ASSIGN(flags, val, assign)              \
+    ({                                                   \
+        const typeof(flags) _flags = (flags);            \
+        typeof(_flags)      _val   = (val);              \
+                                                         \
+        (void) (FALSE && (flags) == (val));              \
+                                                         \
+        (assign) ? (_flags | _val) : (_flags & (~_val)); \
     })
 
-#define NM_FLAGS_ASSIGN_MASK(flags, mask, val) \
-    ({                                         \
-        const typeof(flags) _flags = (flags);  \
-        const typeof(flags) _mask  = (mask);   \
-        const typeof(flags) _val   = (val);    \
-                                               \
-        ((_flags & ~_mask) | (_mask & _val));  \
+#define NM_FLAGS_ASSIGN_MASK(flags, mask, val)  \
+    ({                                          \
+        const typeof(flags) _flags = (flags);   \
+        typeof(_flags)      _mask  = (mask);    \
+        typeof(_flags)      _val   = (val);     \
+                                                \
+        (void) (FALSE && (flags) == (val));     \
+        (void) (FALSE && (flags) == (mask));    \
+                                                \
+        ((_flags & (~_mask)) | (_val & _mask)); \
     })
 
 /*****************************************************************************/
@@ -1078,11 +1086,20 @@ nm_g_variant_equal(GVariant *a, GVariant *b)
 /* mirrors g_ascii_isspace() and what we consider spaces in general. */
 #define NM_ASCII_SPACES " \n\t\r\f"
 
-/* Like NM_ASCII_SPACES, but without "\f" (0x0c, Formfeed Page Break).
- * This is what for example systemd calls WHITESPACE and what it uses to tokenize
- * the kernel command line. */
+/* Like NM_ASCII_SPACES, but without "\f" (0x0c, Formfeed Page Break).  This is
+ * what for example systemd calls WHITESPACE and what it uses to tokenize the
+ * kernel command line. */
 #define NM_ASCII_WHITESPACES " \n\t\r"
 
+/* mirrors <ctype.h>'s isspace() with C locale. It's like NM_ASCII_SPACES but
+ * additionally also considers '\v' (vertical tab). */
+#define NM_ASCII_SPACES_CTYPE NM_ASCII_SPACES "\v"
+
+/* mirrors kernel's isspace() from "include/linux/ctype.h", which treats as
+ * space the common ASCII spaces, including '\v' (vertical tab), but also
+ * '\240' (non-breaking space, NBSP in Latin-1). */
+#define NM_ASCII_SPACES_KERNEL NM_ASCII_SPACES_CTYPE "\240"
+
 static inline gboolean
 nm_ascii_is_whitespace(char ch)
 {
@@ -1092,6 +1109,13 @@ nm_ascii_is_whitespace(char ch)
     return NM_IN_SET(ch, ' ', '\n', '\t', '\r');
 }
 
+static inline gboolean
+nm_ascii_is_space_kernel(char ch)
+{
+    /* Checks whether @ch is in NM_ASCII_SPACES_KERNEL. */
+    return NM_IN_SET(ch, ' ', '\n', '\t', '\r', '\f', '\v', '\240');
+}
+
 #define NM_ASCII_NEWLINE "\n\r"
 
 static inline gboolean
@@ -1490,23 +1514,21 @@ _nm_strndup_a_step(char *s, const char *str, gsize len)
  *
  * Instead, this generic macro is supposed to handle all integers correctly. */
 #if _NM_CC_SUPPORT_GENERIC
-#define nm_strdup_int(val)                                                       \
-    _Generic((val), char                                                         \
-             : g_strdup_printf("%d", (int) (val)),                               \
-                                                                                 \
-               signed char                                                       \
-             : g_strdup_printf("%d", (signed) (val)), signed short               \
-             : g_strdup_printf("%d", (signed) (val)), signed                     \
-             : g_strdup_printf("%d", (signed) (val)), signed long                \
-             : g_strdup_printf("%ld", (signed long) (val)), signed long long     \
-             : g_strdup_printf("%lld", (signed long long) (val)),                \
-                                                                                 \
-               unsigned char                                                     \
-             : g_strdup_printf("%u", (unsigned) (val)), unsigned short           \
-             : g_strdup_printf("%u", (unsigned) (val)), unsigned                 \
-             : g_strdup_printf("%u", (unsigned) (val)), unsigned long            \
-             : g_strdup_printf("%lu", (unsigned long) (val)), unsigned long long \
-             : g_strdup_printf("%llu", (unsigned long long) (val)))
+#define nm_strdup_int(val)                                                   \
+    _Generic((val),                                                          \
+        char: g_strdup_printf("%d", (int) (val)),                            \
+                                                                             \
+        signed char: g_strdup_printf("%d", (signed) (val)),                  \
+        signed short: g_strdup_printf("%d", (signed) (val)),                 \
+        signed: g_strdup_printf("%d", (signed) (val)),                       \
+        signed long: g_strdup_printf("%ld", (signed long) (val)),            \
+        signed long long: g_strdup_printf("%lld", (signed long long) (val)), \
+                                                                             \
+        unsigned char: g_strdup_printf("%u", (unsigned) (val)),              \
+        unsigned short: g_strdup_printf("%u", (unsigned) (val)),             \
+        unsigned: g_strdup_printf("%u", (unsigned) (val)),                   \
+        unsigned long: g_strdup_printf("%lu", (unsigned long) (val)),        \
+        unsigned long long: g_strdup_printf("%llu", (unsigned long long) (val)))
 #else
 #define nm_strdup_int(val)                                        \
     ((sizeof(val) == sizeof(guint64) && ((typeof(val)) -1) > 0)   \
diff --git a/src/libnm-glib-aux/nm-prioq.c b/src/libnm-glib-aux/nm-prioq.c
index 3448dcd9..e74b2b99 100644
--- a/src/libnm-glib-aux/nm-prioq.c
+++ b/src/libnm-glib-aux/nm-prioq.c
@@ -16,15 +16,38 @@
 
 #include "nm-prioq.h"
 
-#include <errno.h>
-#include <stdlib.h>
-
 /*****************************************************************************/
 
-struct _NMPrioqItem {
+typedef struct _NMPrioqItem {
     void     *data;
     unsigned *idx;
-};
+} PrioqItem;
+
+/*****************************************************************************/
+
+#define _nm_assert_q(q)                                         \
+    G_STMT_START                                                \
+    {                                                           \
+        const NMPrioq *const _q2 = (q);                         \
+                                                                \
+        nm_assert(_q2);                                         \
+        nm_assert(_q2->_priv.n_items == 0 || _q2->_priv.items); \
+        nm_assert(_q2->_priv.compare_func);                     \
+    }                                                           \
+    G_STMT_END
+
+#define _nm_assert_item(q, item)                                \
+    G_STMT_START                                                \
+    {                                                           \
+        const NMPrioq *const   _q    = (q);                     \
+        const PrioqItem *const _item = (item);                  \
+                                                                \
+        _nm_assert_q(_q);                                       \
+                                                                \
+        nm_assert(_item >= _q->_priv.items);                    \
+        nm_assert(_item < &_q->_priv.items[_q->_priv.n_items]); \
+    }                                                           \
+    G_STMT_END
 
 /*****************************************************************************/
 
@@ -72,6 +95,15 @@ nm_prioq_destroy(NMPrioq *q)
     if (!q || !q->_priv.compare_func)
         return;
 
+    _nm_assert_q(q);
+
+    while (q->_priv.n_items > 0) {
+        PrioqItem *i = &q->_priv.items[--q->_priv.n_items];
+
+        if (i->idx)
+            *i->idx = NM_PRIOQ_IDX_NULL;
+    }
+
     free(q->_priv.items);
     q->_priv.compare_func = NULL;
 }
@@ -81,8 +113,7 @@ nm_prioq_destroy(NMPrioq *q)
 static int
 compare(NMPrioq *q, unsigned a, unsigned b)
 {
-    nm_assert(q);
-    nm_assert(q->_priv.compare_func);
+    _nm_assert_q(q);
     nm_assert(a != b);
     nm_assert(a < q->_priv.n_items);
     nm_assert(b < q->_priv.n_items);
@@ -99,15 +130,13 @@ compare(NMPrioq *q, unsigned a, unsigned b)
 static void
 swap(NMPrioq *q, unsigned j, unsigned k)
 {
-    nm_assert(q);
+    _nm_assert_q(q);
     nm_assert(j < q->_priv.n_items);
     nm_assert(k < q->_priv.n_items);
-
     nm_assert(!q->_priv.items[j].idx || *(q->_priv.items[j].idx) == j);
     nm_assert(!q->_priv.items[k].idx || *(q->_priv.items[k].idx) == k);
 
-    NM_SWAP(&q->_priv.items[j].data, &q->_priv.items[k].data);
-    NM_SWAP(&q->_priv.items[j].idx, &q->_priv.items[k].idx);
+    NM_SWAP(&q->_priv.items[j], &q->_priv.items[k]);
 
     if (q->_priv.items[j].idx)
         *q->_priv.items[j].idx = j;
@@ -119,7 +148,7 @@ swap(NMPrioq *q, unsigned j, unsigned k)
 static unsigned
 shuffle_up(NMPrioq *q, unsigned idx)
 {
-    nm_assert(q);
+    _nm_assert_q(q);
     nm_assert(idx < q->_priv.n_items);
 
     while (idx > 0) {
@@ -140,7 +169,7 @@ shuffle_up(NMPrioq *q, unsigned idx)
 static unsigned
 shuffle_down(NMPrioq *q, unsigned idx)
 {
-    nm_assert(q);
+    _nm_assert_q(q);
 
     for (;;) {
         unsigned j;
@@ -184,19 +213,21 @@ nm_prioq_put(NMPrioq *q, void *data, unsigned *idx)
 {
     unsigned k;
 
-    nm_assert(q);
+    _nm_assert_q(q);
+    nm_assert(q->_priv.n_items < G_MAXUINT);
 
-    if (q->_priv.n_items >= q->_priv.n_allocated) {
+    if (G_UNLIKELY(q->_priv.n_items >= q->_priv.n_allocated)) {
         q->_priv.n_allocated = NM_MAX((q->_priv.n_items + 1u) * 2u, 16u);
-        q->_priv.items       = g_renew(struct _NMPrioqItem, q->_priv.items, q->_priv.n_allocated);
+        q->_priv.items       = g_renew(PrioqItem, q->_priv.items, q->_priv.n_allocated);
     }
 
     k = q->_priv.n_items++;
 
-    q->_priv.items[k] = (struct _NMPrioqItem){
+    q->_priv.items[k] = (PrioqItem){
         .data = data,
         .idx  = idx,
     };
+
     if (idx)
         *idx = k;
 
@@ -204,71 +235,88 @@ nm_prioq_put(NMPrioq *q, void *data, unsigned *idx)
 }
 
 static void
-remove_item(NMPrioq *q, struct _NMPrioqItem *i)
+remove_item(NMPrioq *q, PrioqItem *i)
 {
-    struct _NMPrioqItem *l;
-    unsigned             k;
+    PrioqItem *l;
+    unsigned   k;
 
-    nm_assert(q);
-    nm_assert(i);
-    nm_assert(q->_priv.n_items > 0);
-    nm_assert(i >= q->_priv.items);
-    nm_assert(i < &q->_priv.items[q->_priv.n_items]);
+    _nm_assert_item(q, i);
 
-    l = &q->_priv.items[q->_priv.n_items - 1u];
+    if (i->idx)
+        *i->idx = NM_PRIOQ_IDX_NULL;
+
+    q->_priv.n_items--;
+
+    l = &q->_priv.items[q->_priv.n_items];
 
     if (i == l) {
-        /* Last entry, let's just remove it */
-        q->_priv.n_items--;
+        /* Last entry, nothing to do. */
         return;
     }
 
-    /* Not last entry, let's replace the last entry with
-     * this one, and reshuffle */
+    /* Not last entry, let's replace this entry with the last one, and
+     * reshuffle */
+
     k = i - q->_priv.items;
 
     *i = *l;
+
     if (i->idx)
         *i->idx = k;
-    q->_priv.n_items--;
 
     k = shuffle_down(q, k);
     shuffle_up(q, k);
 }
 
-_nm_pure static struct _NMPrioqItem *
+static PrioqItem *
 find_item(NMPrioq *q, void *data, unsigned *idx)
 {
-    struct _NMPrioqItem *i;
-
-    nm_assert(q);
+    PrioqItem *i;
 
-    if (q->_priv.n_items <= 0)
-        return NULL;
+    _nm_assert_q(q);
 
-    if (idx) {
-        if (*idx == NM_PRIOQ_IDX_NULL || *idx >= q->_priv.n_items)
-            return NULL;
-
-        i = &q->_priv.items[*idx];
-        if (i->data == data)
-            return i;
-    } else {
+    if (G_UNLIKELY(!idx)) {
+        /* We allow using NMPrioq without "idx". In that case, it does a linear
+         * search for the data. */
         for (i = q->_priv.items; i < &q->_priv.items[q->_priv.n_items]; i++) {
             if (i->data == data)
                 return i;
         }
+        return NULL;
     }
 
-    return NULL;
+    /* If the user however provides an "idx" pointer, then we assert that it is
+     * consistent. That is, if data is not in the queue, then we require that
+     * "*idx" is NM_PRIOQ_IDX_NULL, and otherwise we require that we really
+     * find "data" at index "*idx".
+     *
+     * This means, when the user calls nm_prioq_{remove,update,reshuffle}()
+     * with an "idx", then they must make sure that the index is consistent.
+     * Usually this means they are required to initialize the index to
+     * NM_PRIOQ_IDX_NULL while the data is not in the heap.
+     *
+     * This is done to assert more, and requires a stricter usage of the API
+     * (in the hope to find misuses of the index). */
+
+    if (*idx >= q->_priv.n_items) {
+        nm_assert(*idx == NM_PRIOQ_IDX_NULL);
+        return NULL;
+    }
+
+    i = &q->_priv.items[*idx];
+
+    if (i->data != data)
+        return nm_assert_unreachable_val(NULL);
+
+    return i;
 }
 
 gboolean
 nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx)
 {
-    struct _NMPrioqItem *i;
+    PrioqItem *i;
 
-    nm_assert(q);
+    _nm_assert_q(q);
 
     i = find_item(q, data, idx);
     if (!i)
@@ -278,28 +326,60 @@ nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx)
     return TRUE;
 }
 
+static void
+reshuffle_item(NMPrioq *q, PrioqItem *i)
+{
+    unsigned k;
+
+    _nm_assert_item(q, i);
+
+    k = i - q->_priv.items;
+    k = shuffle_down(q, k);
+    shuffle_up(q, k);
+}
+
 gboolean
 nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx)
 {
-    struct _NMPrioqItem *i;
-    unsigned             k;
+    PrioqItem *i;
 
-    nm_assert(q);
+    _nm_assert_q(q);
 
     i = find_item(q, data, idx);
     if (!i)
         return FALSE;
 
-    k = i - q->_priv.items;
-    k = shuffle_down(q, k);
-    shuffle_up(q, k);
+    reshuffle_item(q, i);
     return TRUE;
 }
 
+void
+nm_prioq_update(NMPrioq *q, void *data, unsigned *idx, bool queued /* or else remove */)
+{
+    PrioqItem *i;
+
+    _nm_assert_q(q);
+
+    i = find_item(q, data, idx);
+
+    if (!i) {
+        if (queued)
+            nm_prioq_put(q, data, idx);
+        return;
+    }
+
+    if (!queued) {
+        remove_item(q, i);
+        return;
+    }
+
+    reshuffle_item(q, i);
+}
+
 void *
 nm_prioq_peek_by_index(NMPrioq *q, unsigned idx)
 {
-    nm_assert(q);
+    _nm_assert_q(q);
 
     if (idx >= q->_priv.n_items)
         return NULL;
@@ -312,7 +392,7 @@ nm_prioq_pop(NMPrioq *q)
 {
     void *data;
 
-    nm_assert(q);
+    _nm_assert_q(q);
 
     if (q->_priv.n_items <= 0)
         return NULL;
diff --git a/src/libnm-glib-aux/nm-prioq.h b/src/libnm-glib-aux/nm-prioq.h
index 918c6447..51e5b059 100644
--- a/src/libnm-glib-aux/nm-prioq.h
+++ b/src/libnm-glib-aux/nm-prioq.h
@@ -43,6 +43,8 @@ void     nm_prioq_put(NMPrioq *q, void *data, unsigned *idx);
 gboolean nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx);
 gboolean nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx);
 
+void nm_prioq_update(NMPrioq *q, void *data, unsigned *idx, bool queued /* or else remove */);
+
 void *nm_prioq_peek_by_index(NMPrioq *q, unsigned idx) _nm_pure;
 
 static inline void *
@@ -53,7 +55,7 @@ nm_prioq_peek(NMPrioq *q)
 
 void *nm_prioq_pop(NMPrioq *q);
 
-#define NM_PRIOQ_FOREACH_ITEM(q, p) for (unsigned _i = 0; (p = nm_prioq_peek_by_index(q, _i)); _i++)
+#define nm_prioq_for_each(q, p) for (unsigned _i = 0; (p = nm_prioq_peek_by_index((q), _i)); _i++)
 
 _nm_pure static inline unsigned
 nm_prioq_size(NMPrioq *q)
diff --git a/src/libnm-glib-aux/nm-random-utils.c b/src/libnm-glib-aux/nm-random-utils.c
index 2050d2f9..bbc5536a 100644
--- a/src/libnm-glib-aux/nm-random-utils.c
+++ b/src/libnm-glib-aux/nm-random-utils.c
@@ -301,7 +301,7 @@ _bad_random_bytes(guint8 *buf, gsize n)
  * nm_random_get_bytes_full:
  * @p: the buffer to fill
  * @n: the number of bytes to write to @p.
- * @out_high_quality: (allow-none) (out): whether the returned
+ * @out_high_quality: (out) (optional): whether the returned
  *   random bytes are of high quality.
  *
  * - will never block
diff --git a/src/libnm-glib-aux/nm-ref-string.h b/src/libnm-glib-aux/nm-ref-string.h
index 1b0cabf2..c7cfe87f 100644
--- a/src/libnm-glib-aux/nm-ref-string.h
+++ b/src/libnm-glib-aux/nm-ref-string.h
@@ -53,7 +53,7 @@ nmtst_ref_string_find(const char *cstr)
 {
     /* WARNING: only use for testing. See nmtst_ref_string_find_len() why. */
     if (!cstr)
-        return FALSE;
+        return NULL;
     return nmtst_ref_string_find_len(cstr, strlen(cstr));
 }
 
diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c
index 702a63e9..34a3af20 100644
--- a/src/libnm-glib-aux/nm-shared-utils.c
+++ b/src/libnm-glib-aux/nm-shared-utils.c
@@ -20,7 +20,9 @@
 
 #include "c-list/src/c-list.h"
 #include "nm-errno.h"
+#include "nm-time-utils.h"
 #include "nm-str-buf.h"
+#include "nm-time-utils.h"
 
 G_STATIC_ASSERT(sizeof(NMEtherAddr) == 6);
 G_STATIC_ASSERT(_nm_alignof(NMEtherAddr) == 1);
@@ -64,7 +66,7 @@ nm_ether_addr_from_string(NMEtherAddr *addr, const char *str)
  *
  * Checks if only the bottom 64bits of the address are set.
  *
- * Return value: %TRUE or %FALSE
+ * Returns: %TRUE or %FALSE
  */
 gboolean
 _nm_utils_inet6_is_token(const struct in6_addr *in6addr)
@@ -437,7 +439,7 @@ nm_g_bytes_new_from_variant_ay(GVariant *var)
 
 /**
  * nm_g_bytes_equal_mem:
- * @bytes: (allow-none): a #GBytes array to compare. Note that
+ * @bytes: (nullable): a #GBytes array to compare. Note that
  *   %NULL is treated like an #GBytes array of length zero.
  * @mem_data: the data pointer with @mem_len bytes
  * @mem_len: the length of the data pointer
@@ -692,7 +694,7 @@ nm_strdict_to_variant_asv(GHashTable *strdict)
  * nm_strquote:
  * @buf: the output buffer of where to write the quoted @str argument.
  * @buf_len: the size of @buf.
- * @str: (allow-none): the string to quote.
+ * @str: (nullable): the string to quote.
  *
  * Writes @str to @buf with quoting. The resulting buffer
  * is always NUL terminated, unless @buf_len is zero.
@@ -1764,11 +1766,11 @@ nm_utils_escaped_tokens_escape_full(const char                     *str,
  * nm_utils_escaped_tokens_options_split:
  * @str: the src string. This string will be modified in-place.
  *   The output values will point into @str.
- * @out_key: (allow-none): the returned output key. This will always be set to @str
- *   itself. @str will be modified to contain only the unescaped, truncated
- *   key name.
- * @out_val: returns the parsed (and unescaped) value or %NULL, if @str contains
- *   no '=' delimiter.
+ * @out_key: (out) (nullable): the returned output key. This will always be set
+ *   to @str itself. @str will be modified to contain only the unescaped,
+ *   truncated key name.
+ * @out_val: (out) (nullable): returns the parsed (and unescaped) value or
+ *   %NULL, if @str contains no '=' delimiter.
  *
  * Honors backslash escaping to parse @str as "key=value" pairs. Optionally, if no '='
  * is present, @out_val will be returned as %NULL. Backslash can be used to escape
@@ -2224,7 +2226,7 @@ nm_utils_error_is_notfound(GError *error)
  * @object: the target object
  * @property_name: the property name
  * @value: the #GValue to set
- * @error: (allow-none): optional error argument
+ * @error: optional error argument
  *
  * A reimplementation of g_object_set_property(), but instead
  * returning an error instead of logging a warning. All g_object_set*()
@@ -2524,7 +2526,7 @@ _str_buf_append_c_escape_octal(NMStrBuf *strbuf, char ch)
 
 /**
  * nm_utils_buf_utf8safe_unescape:
- * @str: (allow-none): the string to unescape. The string itself is a NUL terminated
+ * @str: (nullable): the string to unescape. The string itself is a NUL terminated
  *   ASCII string, that can have C-style backslash escape sequences (which
  *   are to be unescaped). Non-ASCII characters (e.g. UTF-8) are taken verbatim, so
  *   it doesn't care that this string is UTF-8. However, usually this is a UTF-8 encoded
@@ -2755,13 +2757,16 @@ nm_utils_buf_utf8safe_escape(gconstpointer           buf,
     if (g_utf8_validate(str, buflen, &p) && nul_terminated) {
         /* note that g_utf8_validate() does not allow NUL character inside @str. Good.
          * We can treat @str like a NUL terminated string. */
-        if (!NM_STRCHAR_ANY(str,
-                            ch,
-                            (ch == '\\'
-                             || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)
-                                 && nm_ascii_is_ctrl_or_del(ch))
-                             || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
-                                 && nm_ascii_is_non_ascii(ch)))))
+        if (!NM_STRCHAR_ANY(
+                str,
+                ch,
+                (ch == '\\'
+                 || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)
+                     && nm_ascii_is_ctrl_or_del(ch))
+                 || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
+                     && nm_ascii_is_non_ascii(ch))
+                 || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_DOUBLE_QUOTE)
+                     && ch == '"'))))
             return str;
     }
 
@@ -2781,7 +2786,9 @@ nm_utils_buf_utf8safe_escape(gconstpointer           buf,
             else if ((NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL)
                       && nm_ascii_is_ctrl_or_del(ch))
                      || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII)
-                         && nm_ascii_is_non_ascii(ch)))
+                         && nm_ascii_is_non_ascii(ch))
+                     || (NM_FLAGS_HAS(flags, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_DOUBLE_QUOTE)
+                         && ch == '"'))
                 _str_buf_append_c_escape_octal(&strbuf, ch);
             else
                 nm_str_buf_append_c(&strbuf, ch);
@@ -3332,9 +3339,9 @@ _utils_hashtable_equal(GHashTable      *hash_a,
 
 /**
  * nm_utils_hashtable_cmp_equal:
- * @a: (allow-none): the hash table or %NULL
- * @b: (allow-none): the other hash table or %NULL
- * @cmp_values: (allow-none): if %NULL, only the keys
+ * @a: (nullable): the hash table or %NULL
+ * @b: (nullable): the other hash table or %NULL
+ * @cmp_values: (nullable): if %NULL, only the keys
  *   will be compared. Otherwise, this function is used to
  *   check whether all keys are equal.
  * @user_data: the argument for @cmp_values.
@@ -3401,8 +3408,8 @@ _hashtable_cmp_func(gconstpointer a, gconstpointer b, gpointer user_data)
 
 /**
  * nm_utils_hashtable_cmp:
- * @a: (allow-none): the hash to compare. May be %NULL.
- * @b: (allow-none): the other hash to compare. May be %NULL.
+ * @a: (nullable): the hash to compare. May be %NULL.
+ * @b: (nullable): the other hash to compare. May be %NULL.
  * @do_fast_precheck: if %TRUE, assume that the hashes are equal
  *   and that it is worth calling nm_utils_hashtable_cmp_equal() first.
  *   That requires, that both hashes have the same equals function
@@ -3410,7 +3417,7 @@ _hashtable_cmp_func(gconstpointer a, gconstpointer b, gpointer user_data)
  * @cmp_keys: the compare function for keys. Usually, the hash/equal function
  *   of both hashes corresponds to this function. If you set @do_fast_precheck
  *   to false, then this is not a requirement.
- * @cmp_values: (allow-none): if %NULL, only the keys are compared.
+ * @cmp_values: (nullable): if %NULL, only the keys are compared.
  *   Otherwise, the values must are also compared with this function.
  *
  * Both hashes must have keys/values of the same domain, so that
@@ -4839,6 +4846,50 @@ nm_g_child_watch_source_new(GPid            pid,
     return source;
 }
 
+gboolean
+nm_g_timeout_reschedule(GSource   **src,
+                        gint64     *p_expiry_msec,
+                        gint64      expiry_msec,
+                        GSourceFunc func,
+                        gpointer    user_data)
+{
+    gint64 now_msec;
+    gint64 timeout_msec;
+
+    /* (Re-)Schedules a timeout at "expiry_msec" (in
+     * nm_utils_get_monotonic_timestamp_msec() scale).
+     *
+     * If a source is already scheduled in "*src" and "*p_expiry_msec" is
+     * identical to "expiry_msec", then we assume the timer is already ticking,
+     * and nothing is rescheduled.
+     *
+     * Otherwise, "*src" gets cancelled (if any), a new timer is scheduled
+     * (assigned to "*src") and the new expiry is written to "*p_expiry_msec".
+     */
+
+    nm_assert(src);
+    nm_assert(p_expiry_msec);
+
+    if (*src) {
+        if (*p_expiry_msec == expiry_msec) {
+            /* already scheduled with same expiry. */
+            return FALSE;
+        }
+        nm_clear_g_source_inst(src);
+    }
+
+    now_msec = nm_utils_get_monotonic_timestamp_msec();
+
+    if (expiry_msec <= now_msec)
+        timeout_msec = 0;
+    else
+        timeout_msec = NM_MIN(expiry_msec - now_msec, (gint64) G_MAXUINT);
+
+    *p_expiry_msec = expiry_msec;
+    *src           = nm_g_timeout_add_source(timeout_msec, func, user_data);
+    return TRUE;
+}
+
 /*****************************************************************************/
 
 #define _CTX_LOG(fmt, ...)                                                                       \
@@ -5301,7 +5352,7 @@ nm_utils_ifname_valid_kernel(const char *name, GError **error)
 
         if (ch == '\0')
             return TRUE;
-        if (NM_IN_SET(ch, '/', ':') || g_ascii_isspace(ch)) {
+        if (NM_IN_SET(ch, '/', ':') || nm_ascii_is_space_kernel(ch)) {
             g_set_error_literal(error,
                                 NM_UTILS_ERROR,
                                 NM_UTILS_ERROR_UNKNOWN,
@@ -6729,7 +6780,7 @@ valid_ldh_char(char c)
  * @s: the hostname to check.
  * @trailing_dot: Accept trailing dot on multi-label names.
  *
- * Return: %TRUE if valid.
+ * Returns: %TRUE if valid.
  */
 gboolean
 nm_hostname_is_valid(const char *s, gboolean trailing_dot)
@@ -6789,3 +6840,263 @@ nm_hostname_is_valid(const char *s, gboolean trailing_dot)
 
     return TRUE;
 }
+
+/*****************************************************************************/
+
+typedef struct {
+    GTask                    *task;
+    GSource                  *source_timeout;
+    GSource                  *source_next_poll;
+    GMainContext             *context;
+    GCancellable             *internal_cancellable;
+    NMUtilsPollProbeStartFcn  probe_start_fcn;
+    NMUtilsPollProbeFinishFcn probe_finish_fcn;
+    gpointer                  probe_user_data;
+    gulong                    cancellable_id;
+    gint64                    last_poll_start_ms;
+    int                       sleep_timeout_ms;
+    int                       ratelimit_timeout_ms;
+    bool                      completed : 1;
+} PollTaskData;
+
+static void
+_poll_task_data_free(gpointer data)
+{
+    PollTaskData *poll_task_data = data;
+
+    nm_assert(G_IS_TASK(poll_task_data->task));
+    nm_assert(!poll_task_data->source_next_poll);
+    nm_assert(!poll_task_data->source_timeout);
+    nm_assert(poll_task_data->cancellable_id == 0);
+
+    g_main_context_unref(poll_task_data->context);
+
+    nm_g_slice_free(poll_task_data);
+}
+
+static void
+_poll_return(PollTaskData *poll_task_data, GError *error_take)
+{
+    nm_clear_g_source_inst(&poll_task_data->source_next_poll);
+    nm_clear_g_source_inst(&poll_task_data->source_timeout);
+    nm_clear_g_cancellable_disconnect(g_task_get_cancellable(poll_task_data->task),
+                                      &poll_task_data->cancellable_id);
+
+    nm_clear_g_cancellable(&poll_task_data->internal_cancellable);
+
+    if (error_take)
+        g_task_return_error(poll_task_data->task, g_steal_pointer(&error_take));
+    else
+        g_task_return_boolean(poll_task_data->task, TRUE);
+
+    g_object_unref(poll_task_data->task);
+}
+
+static gboolean _poll_start_cb(gpointer user_data);
+
+static void
+_poll_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
+{
+    PollTaskData                     *poll_task_data = user_data;
+    _nm_unused gs_unref_object GTask *task =
+        poll_task_data->task; /* balance ref from _poll_start_cb() */
+    gs_free_error GError *error = NULL;
+    gint64                now_ms;
+    gint64                wait_ms;
+    gboolean              is_finished;
+
+    is_finished =
+        poll_task_data->probe_finish_fcn(source, result, poll_task_data->probe_user_data, &error);
+
+    if (nm_utils_error_is_cancelled(error)) {
+        /* we already handle this differently. Nothing to do. */
+        return;
+    }
+
+    if (error || is_finished) {
+        _poll_return(poll_task_data, g_steal_pointer(&error));
+        return;
+    }
+
+    now_ms = nm_utils_get_monotonic_timestamp_msec();
+    if (poll_task_data->ratelimit_timeout_ms > 0)
+        wait_ms =
+            (poll_task_data->last_poll_start_ms + poll_task_data->ratelimit_timeout_ms) - now_ms;
+    else
+        wait_ms = 0;
+    if (poll_task_data->sleep_timeout_ms > 0)
+        wait_ms = MAX(wait_ms, poll_task_data->sleep_timeout_ms);
+
+    poll_task_data->source_next_poll =
+        nm_g_source_attach(nm_g_timeout_source_new(MAX(1, wait_ms),
+                                                   G_PRIORITY_DEFAULT,
+                                                   _poll_start_cb,
+                                                   poll_task_data,
+                                                   NULL),
+                           poll_task_data->context);
+}
+
+static gboolean
+_poll_start_cb(gpointer user_data)
+{
+    PollTaskData *poll_task_data = user_data;
+
+    nm_clear_g_source_inst(&poll_task_data->source_next_poll);
+
+    poll_task_data->last_poll_start_ms = nm_utils_get_monotonic_timestamp_msec();
+
+    g_object_ref(poll_task_data->task); /* balanced by _poll_done_cb() */
+
+    poll_task_data->probe_start_fcn(poll_task_data->internal_cancellable,
+                                    poll_task_data->probe_user_data,
+                                    _poll_done_cb,
+                                    poll_task_data);
+
+    return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+_poll_timeout_cb(gpointer user_data)
+{
+    PollTaskData *poll_task_data = user_data;
+
+    _poll_return(poll_task_data, nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "timeout expired"));
+    return G_SOURCE_CONTINUE;
+}
+
+static void
+_poll_cancelled_cb(GObject *object, gpointer user_data)
+{
+    PollTaskData *poll_task_data = user_data;
+    GError       *error          = NULL;
+
+    nm_clear_g_signal_handler(g_task_get_cancellable(poll_task_data->task),
+                              &poll_task_data->cancellable_id);
+    nm_utils_error_set_cancelled(&error, FALSE, NULL);
+    _poll_return(poll_task_data, error);
+}
+
+/**
+ * nm_utils_poll:
+ * @poll_timeout_ms: if >= 0, then this is the overall timeout for how long we poll.
+ *   When this timeout expires, the request completes with failure (and error set).
+ * @ratelimit_timeout_ms: if > 0, we ratelimit the starts from one prope_start_fcn
+ *   call to the next. We will wait at least this time between two consecutive polls.
+ * @sleep_timeout_ms: if > 0, then we wait after a probe finished this timeout
+ *   before the next. Together with @ratelimit_timeout_ms this determines how
+ *   frequently we probe. We will wait at least this time between the end of the
+ *   previous poll and the next one.
+ * @probe_register_object_fcn: (allow-none): called by nm_utils_poll()
+ *   synchronously, with the new, internal GTask instance. The purpose of this
+ *   callback is a bit obscure, you may want to pass NULL here. It's used by some
+ *   caller to register a weak pointer on the internal GTask instance to track
+ *   the lifetime of the operation.
+ * @probe_start_fcn: used to start a (asynchronous) probe. A probe must be
+ *   completed by calling the provided callback. While a probe is in progress, we
+ *   will not start another. The function is called the first time on an idle
+ *   handler, afterwards it gets called again on each timeout for polling.
+ * @probe_finish_fcn: will be called from the callback of @probe_start_fcn. If the
+ *   function returns %TRUE (polling done) or an error, polling stops. Otherwise,
+ *   another poll will be started.
+ * @probe_user_data: user_data for the probe functions.
+ * @cancellable: cancellable for polling.
+ * @callback: when polling completes.
+ * @user_data: for @callback.
+ *
+ * This uses the current g_main_context_get_thread_default() for scheduling
+ * actions.
+ */
+void
+nm_utils_poll(int                               poll_timeout_ms,
+              int                               ratelimit_timeout_ms,
+              int                               sleep_timeout_ms,
+              NMUtilsPollProbeRegisterObjectFcn probe_register_object_fcn,
+              NMUtilsPollProbeStartFcn          probe_start_fcn,
+              NMUtilsPollProbeFinishFcn         probe_finish_fcn,
+              gpointer                          probe_user_data,
+              GCancellable                     *cancellable,
+              GAsyncReadyCallback               callback,
+              gpointer                          user_data)
+{
+    PollTaskData *poll_task_data;
+
+    poll_task_data  = g_slice_new(PollTaskData);
+    *poll_task_data = (PollTaskData){
+        .task             = nm_g_task_new(NULL, cancellable, nm_utils_poll, callback, user_data),
+        .probe_start_fcn  = probe_start_fcn,
+        .probe_finish_fcn = probe_finish_fcn,
+        .probe_user_data  = probe_user_data,
+        .completed        = FALSE,
+        .context          = g_main_context_ref_thread_default(),
+        .sleep_timeout_ms = sleep_timeout_ms,
+        .ratelimit_timeout_ms = ratelimit_timeout_ms,
+        .internal_cancellable = g_cancellable_new(),
+    };
+
+    g_task_set_task_data(poll_task_data->task, poll_task_data, _poll_task_data_free);
+
+    if (probe_register_object_fcn)
+        probe_register_object_fcn((GObject *) poll_task_data->task, probe_user_data);
+
+    if (poll_timeout_ms >= 0) {
+        poll_task_data->source_timeout =
+            nm_g_source_attach(nm_g_timeout_source_new(poll_timeout_ms,
+                                                       G_PRIORITY_DEFAULT,
+                                                       _poll_timeout_cb,
+                                                       poll_task_data,
+                                                       NULL),
+                               poll_task_data->context);
+    }
+
+    poll_task_data->source_next_poll = nm_g_source_attach(
+        nm_g_idle_source_new(G_PRIORITY_DEFAULT_IDLE, _poll_start_cb, poll_task_data, NULL),
+        poll_task_data->context);
+
+    if (cancellable) {
+        gulong signal_id;
+
+        signal_id = g_cancellable_connect(cancellable,
+                                          G_CALLBACK(_poll_cancelled_cb),
+                                          poll_task_data,
+                                          NULL);
+        if (signal_id == 0) {
+            /* the request is already cancelled. Return. */
+            return;
+        }
+        poll_task_data->cancellable_id = signal_id;
+    }
+}
+
+/**
+ * nm_utils_poll_finish:
+ * @result: the GAsyncResult from the GAsyncReadyCallback callback.
+ * @probe_user_data: the user data provided to nm_utils_poll().
+ * @error: the failure code.
+ *
+ * Returns: %TRUE if the polling completed with success. In that case,
+ *   the error won't be set.
+ *   If the request was cancelled, this is indicated by @error and
+ *   %FALSE will be returned.
+ *   If the probe returned a failure, this returns %FALSE and the error
+ *   provided by @probe_finish_fcn.
+ *   If the request times out, this returns %FALSE with error set.
+ *   Error is always set if (and only if) the function returns %FALSE.
+ */
+gboolean
+nm_utils_poll_finish(GAsyncResult *result, gpointer *probe_user_data, GError **error)
+{
+    GTask        *task;
+    PollTaskData *poll_task_data;
+
+    g_return_val_if_fail(nm_g_task_is_valid(result, NULL, nm_utils_poll), FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    task = G_TASK(result);
+
+    if (probe_user_data) {
+        poll_task_data = g_task_get_task_data(task);
+        NM_SET_OUT(probe_user_data, poll_task_data->probe_user_data);
+    }
+
+    return g_task_propagate_boolean(task, error);
+}
diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h
index d2e30504..e37cf5e6 100644
--- a/src/libnm-glib-aux/nm-shared-utils.h
+++ b/src/libnm-glib-aux/nm-shared-utils.h
@@ -79,7 +79,7 @@ G_STATIC_ASSERT(sizeof(int) == sizeof(gint32));
     ({                                             \
         _nm_unused typeof(value) _value = (value); \
                                                    \
-        _Generic((value), int : TRUE);             \
+        _Generic((value), int: TRUE);              \
     })
 #else
 #define _NM_INT_LE_MAXINT32(value)                   \
@@ -208,12 +208,13 @@ typedef struct {
 
 #define NM_ETHER_ADDR_INIT(...) ((NMEtherAddr) _NM_ETHER_ADDR_INIT(__VA_ARGS__))
 
-struct _NMIPAddr;
-extern const struct _NMIPAddr nm_ip_addr_zero;
+union _NMIPAddr;
+
+extern const union _NMIPAddr nm_ip_addr_zero;
 
 /* Let's reuse nm_ip_addr_zero also for nm_ether_addr_zero. It's a union that
  * also contains a NMEtherAddr field. */
-#define nm_ether_addr_zero (*((const NMEtherAddr *) ((gconstpointer) &nm_ip_addr_zero)))
+#define nm_ether_addr_zero (*((const NMEtherAddr *) &nm_ip_addr_zero))
 
 static inline int
 nm_ether_addr_cmp(const NMEtherAddr *a, const NMEtherAddr *b)
@@ -1251,12 +1252,16 @@ typedef enum {
      * It will backslash escape ascii characters according to nm_ascii_is_non_ascii(). */
     NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII = 0x0002,
 
+    /* Escape '"' as ASCII "\\042". This is useful when escaping a string so that
+     * it can be unescaped with `echo -e $PASTE_TEXT`. */
+    NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_DOUBLE_QUOTE = 0x0004,
+
     /* This flag only has an effect during escaping to ensure we
      * don't leak secrets in memory. Note that during unescape we
      * know the maximum result size from the beginning, and no
      * reallocation happens. Thus, unescape always avoids leaking
      * secrets already. */
-    NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET = 0x0004,
+    NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET = 0x0008,
 
     /* This flag only has an effect during unescaping. It means
      * that non-escaped whitespaces (g_ascii_isspace()) will be
@@ -1264,7 +1269,7 @@ typedef enum {
      * this flag is only useful for gracefully accepting user input
      * with spaces. With this flag, escape and unescape may no longer
      * yield the original input. */
-    NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES = 0x0008,
+    NM_UTILS_STR_UTF8_SAFE_UNESCAPE_STRIP_SPACES = 0x0010,
 } NMUtilsStrUtf8SafeFlags;
 
 const char *nm_utils_buf_utf8safe_escape(gconstpointer           buf,
@@ -1352,10 +1357,6 @@ nm_g_variant_new_au(const guint32 *data, gsize len)
     return g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, data, len, sizeof(guint32));
 }
 
-struct _NMIPAddr;
-
-extern const struct _NMIPAddr nm_ip_addr_zero;
-
 static inline GVariant *
 nm_g_variant_new_ay_inaddr(int addr_family, gconstpointer addr)
 {
@@ -1554,6 +1555,12 @@ nm_g_timeout_add_source(guint timeout_msec, GSourceFunc func, gpointer user_data
         NULL);
 }
 
+gboolean nm_g_timeout_reschedule(GSource   **src,
+                                 gint64     *p_expiry_msec,
+                                 gint64      expiry_msec,
+                                 GSourceFunc func,
+                                 gpointer    user_data);
+
 static inline GSource *
 nm_g_timeout_add_seconds_source(guint timeout_sec, GSourceFunc func, gpointer user_data)
 {
@@ -2724,7 +2731,7 @@ guint8 *nm_utils_hexstr2bin_alloc(const char *hexstr,
  * can parse addresses of any length. That is, you don't need
  * to know the length before-hand.
  *
- * Return value: @buffer, or %NULL if @asc couldn't be parsed.
+ * Returns: @buffer, or %NULL if @asc couldn't be parsed.
  */
 static inline guint8 *
 _nm_utils_hwaddr_aton(const char *asc, gpointer buffer, gsize buffer_length, gsize *out_length)
@@ -3250,4 +3257,31 @@ nm_path_startswith(const char *path, const char *prefix)
 
 gboolean nm_hostname_is_valid(const char *s, gboolean trailing_dot);
 
+/*****************************************************************************/
+
+typedef void (*NMUtilsPollProbeRegisterObjectFcn)(GObject *object, gpointer user_data);
+
+typedef void (*NMUtilsPollProbeStartFcn)(GCancellable       *cancellable,
+                                         gpointer            probe_user_data,
+                                         GAsyncReadyCallback callback,
+                                         gpointer            user_data);
+
+typedef gboolean (*NMUtilsPollProbeFinishFcn)(GObject      *source,
+                                              GAsyncResult *result,
+                                              gpointer      probe_user_data,
+                                              GError      **error);
+
+void nm_utils_poll(int                               poll_timeout_ms,
+                   int                               ratelimit_timeout_ms,
+                   int                               sleep_timeout_ms,
+                   NMUtilsPollProbeRegisterObjectFcn probe_register_object_fcn,
+                   NMUtilsPollProbeStartFcn          probe_start_fcn,
+                   NMUtilsPollProbeFinishFcn         probe_finish_fcn,
+                   gpointer                          probe_user_data,
+                   GCancellable                     *cancellable,
+                   GAsyncReadyCallback               callback,
+                   gpointer                          user_data);
+
+gboolean nm_utils_poll_finish(GAsyncResult *result, gpointer *probe_user_data, GError **error);
+
 #endif /* __NM_SHARED_UTILS_H__ */
diff --git a/src/libnm-glib-aux/nm-str-buf.h b/src/libnm-glib-aux/nm-str-buf.h
index 32ab8da8..c62381e5 100644
--- a/src/libnm-glib-aux/nm-str-buf.h
+++ b/src/libnm-glib-aux/nm-str-buf.h
@@ -522,8 +522,8 @@ nm_str_buf_get_char(const NMStrBuf *strbuf, gsize index)
 
 /**
  * nm_str_buf_finalize:
- * @strbuf: an initilized #NMStrBuf
- * @out_len: (out): (allow-none): optional output
+ * @strbuf: an initialized #NMStrBuf
+ * @out_len: (out): (optional): optional output
  *   argument with the length of the returned string.
  *
  * Returns: (transfer full): the string of the buffer
diff --git a/src/libnm-glib-aux/nm-test-utils.h b/src/libnm-glib-aux/nm-test-utils.h
index b65818e0..de6fd0f1 100644
--- a/src/libnm-glib-aux/nm-test-utils.h
+++ b/src/libnm-glib-aux/nm-test-utils.h
@@ -203,6 +203,54 @@
     }                                                             \
     G_STMT_END
 
+#define nmtst_assert_cmpmem(m1, l1, m2, l2)                                                     \
+    G_STMT_START                                                                                \
+    {                                                                                           \
+        const guint8 *const _m1 = (gpointer) (m1);                                              \
+        const guint8 *const _m2 = (gpointer) (m2);                                              \
+        const gsize         _l1 = (l1);                                                         \
+        const gsize         _l2 = (l2);                                                         \
+                                                                                                \
+        /* This is like g_assert_cmpmem(), however on failure it actually
+         * prints the compared buffer contents, which is useful for debugging
+         * the test failure. */                       \
+                                                                                                \
+        g_assert(_l1 == 0 || _m1);                                                              \
+        g_assert(_l2 == 0 || _m2);                                                              \
+                                                                                                \
+        if (_l1 != _l2 || (_l1 > 0 && memcmp(_m1, _m2, _l1) != 0)) {                            \
+            gs_free char *_s1 = NULL;                                                           \
+            gs_free char *_s2 = NULL;                                                           \
+                                                                                                \
+            g_error(                                                                            \
+                "ERROR: %s:%d : buffer [\"%s\" (%s, %zu bytes)] differs from [\"%s\" (%s, %zu " \
+                "bytes)]:\n"                                                                    \
+                "   a=[ \"%s\" ]\n"                                                             \
+                "   b=[ \"%s\" ]\n",                                                            \
+                __FILE__,                                                                       \
+                (int) __LINE__,                                                                 \
+                #m1,                                                                            \
+                #l1,                                                                            \
+                _l1,                                                                            \
+                #m2,                                                                            \
+                #l2,                                                                            \
+                _l2,                                                                            \
+                (_s1 = nm_utils_buf_utf8safe_escape_cp(                                         \
+                     _m1,                                                                       \
+                     _l1,                                                                       \
+                     NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL                                    \
+                         | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_DOUBLE_QUOTE))                    \
+                    ?: "",                                                                      \
+                (_s2 = nm_utils_buf_utf8safe_escape_cp(                                         \
+                     _m2,                                                                       \
+                     _l2,                                                                       \
+                     NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL                                    \
+                         | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_DOUBLE_QUOTE))                    \
+                    ?: "");                                                                     \
+        }                                                                                       \
+    }                                                                                           \
+    G_STMT_END
+
 /*****************************************************************************/
 
 /* Our nm-error error numbers use negative values to signal failure.
@@ -1249,7 +1297,7 @@ nmtst_stable_rand(guint64 seed, gpointer buf, gsize len)
 
 /**
  * nmtst_get_rand_word_length:
- * @rand: (allow-none): #GRand instance or %NULL to use the singleton.
+ * @rand: (nullable): #GRand instance or %NULL to use the singleton.
  *
  * Returns: a random integer >= 0, that most frequently is somewhere between
  * 0 and 16, but (with decreasing) probability, it can be larger. This can
@@ -2839,8 +2887,7 @@ _nmtst_variant_new_vardict(int dummy, ...)
     G_STMT_END
 #else
 #define _nmtst_assert_variant_bytestring_cmp_str(_ptr, _ptr2, _len) \
-    G_STMT_START                                                    \
-    {}                                                              \
+    G_STMT_START {}                                                 \
     G_STMT_END
 #endif
 
diff --git a/src/libnm-glib-aux/nm-uuid.c b/src/libnm-glib-aux/nm-uuid.c
index cdfa5f62..df1b10c0 100644
--- a/src/libnm-glib-aux/nm-uuid.c
+++ b/src/libnm-glib-aux/nm-uuid.c
@@ -411,7 +411,7 @@ nm_uuid_generate_from_string_str(const char   *s,
  * @uuid_type: the UUID type to use. Prefer version 5 unless you have
  *   good reasons.
  * @type_args: the namespace UUID.
- * @strv: (allow-none): the strv list to hash. Can be NULL, in which
+ * @strv: (nullable): the strv list to hash. Can be NULL, in which
  *   case the result is different from an empty array.
  * @len: if negative, @strv is a NULL terminated array. Otherwise,
  *   it is the length of the strv array. In the latter case it may
diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c
index 3eaca547..84d87d48 100644
--- a/src/libnm-glib-aux/tests/test-shared-general.c
+++ b/src/libnm-glib-aux/tests/test-shared-general.c
@@ -2313,6 +2313,130 @@ test_inet_utils(void)
 
 /*****************************************************************************/
 
+static gboolean
+_inet_parse(int addr_family, const char *str, gboolean accept_legacy, gpointer out_addr)
+{
+    int        addr_family2   = -1;
+    int *const p_addr_family2 = nmtst_get_rand_bool() ? &addr_family2 : NULL;
+    NMIPAddr   addr;
+    gboolean   success;
+
+    g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
+
+    success =
+        nm_inet_parse_bin_full((p_addr_family2 && nmtst_get_rand_bool()) ? AF_UNSPEC : addr_family,
+                               accept_legacy,
+                               str,
+                               p_addr_family2,
+                               &addr);
+
+    if (success) {
+        g_assert(!p_addr_family2 || NM_IN_SET(*p_addr_family2, AF_INET, AF_INET6));
+        if (p_addr_family2 && *p_addr_family2 != addr_family) {
+            success = FALSE;
+        } else
+            g_assert(!p_addr_family2 || *p_addr_family2 == addr_family);
+    } else
+        g_assert(addr_family2 == -1);
+
+    if (out_addr && success)
+        nm_ip_addr_set(addr_family, out_addr, &addr);
+
+    return success;
+}
+
+#define _inet_parse_fail(check, accept_legacy)                             \
+    G_STMT_START                                                           \
+    {                                                                      \
+        NMIPAddr _addr;                                                    \
+        gboolean _success;                                                 \
+                                                                           \
+        _success = _inet_parse(nmtst_get_rand_bool() ? AF_INET : AF_INET6, \
+                               "" check "",                                \
+                               (accept_legacy),                            \
+                               nmtst_get_rand_bool() ? &_addr : NULL);     \
+        g_assert(!_success);                                               \
+    }                                                                      \
+    G_STMT_END
+
+#define _inet_parse_good(check, expected, accept_legacy)                        \
+    G_STMT_START                                                                \
+    {                                                                           \
+        int               _accept_legacy = (accept_legacy);                     \
+        const char *const _check         = "" check "";                         \
+        const char *const _expected      = expected ?: _check;                  \
+        NMIPAddr          _addr[2];                                             \
+        gboolean          _success[2];                                          \
+                                                                                \
+        if (_accept_legacy == -1)                                               \
+            _accept_legacy = nmtst_get_rand_bool();                             \
+                                                                                \
+        _success[0] = _inet_parse(AF_INET6, _check, _accept_legacy, &_addr[0]); \
+        _success[1] = _inet_parse(AF_INET, _check, _accept_legacy, &_addr[1]);  \
+                                                                                \
+        g_assert(NM_IN_SET(_success[0], FALSE, TRUE));                          \
+        g_assert(NM_IN_SET(_success[1], FALSE, TRUE));                          \
+        g_assert(_success[0] != _success[1]);                                   \
+                                                                                \
+        if (_success[0])                                                        \
+            nmtst_assert_ip6_address(&_addr[0].addr6, _expected);               \
+        else                                                                    \
+            nmtst_assert_ip4_address(_addr[1].addr4, _expected);                \
+                                                                                \
+        if (_success[1]) {                                                      \
+            in_addr_t _a4;                                                      \
+            int       _r;                                                       \
+                                                                                \
+            _r = nmtst_inet_aton(_check, &_a4);                                 \
+            g_assert_cmpint(_r, ==, 0);                                         \
+            nmtst_assert_ip4_address(_a4, _expected);                           \
+        }                                                                       \
+    }                                                                           \
+    G_STMT_END
+
+static void
+test_inet_parse_ip4_legacy(void)
+{
+    _inet_parse_fail("", -1);
+    _inet_parse_fail(" ", -1);
+    _inet_parse_fail("a", -1);
+    _inet_parse_fail("0", -1);
+    _inet_parse_fail("0.1", -1);
+    _inet_parse_fail("0.4.1", -1);
+    _inet_parse_fail("1.2.3.05", FALSE);
+    _inet_parse_fail("192.000.002.010", FALSE);
+    _inet_parse_fail("1.2.3..5", -1);
+    _inet_parse_fail("1.2.3.0x", -1);
+    _inet_parse_fail("0xC0000234", -1);
+    _inet_parse_fail("192.0.2.2X", -1);
+    _inet_parse_fail("192.0.2.3 Y", -1);
+    _inet_parse_fail("192.0.2.4\nZ", -1);
+    _inet_parse_fail("192.0.2.5\tT", -1);
+    _inet_parse_fail("192.0.2.6 Y", -1);
+    _inet_parse_fail("192.0.2.7\n", -1);
+    _inet_parse_fail("192.0.2.7\t", -1);
+    _inet_parse_fail("192.0.2.7 ", -1);
+    _inet_parse_fail("00x0019.0000001.000000.0x1", -1);
+    _inet_parse_fail("192.0.2.7.", -1);
+    _inet_parse_fail("192.0.2.7.0", -1);
+
+    _inet_parse_good("192.0.2.1", NULL, -1);
+    _inet_parse_good("1.2.3.4", NULL, -1);
+    _inet_parse_good("192.167.3.4", NULL, -1);
+
+    _inet_parse_good("192.000.002.010", "192.0.2.8", TRUE);
+    _inet_parse_good("255.000.000.000", "255.0.0.0", TRUE);
+    _inet_parse_good("1.2.3.05", "1.2.3.5", TRUE);
+    _inet_parse_good("01.2.3.05", "1.2.3.5", TRUE);
+    _inet_parse_good("192.00167.0003.4", "192.119.3.4", TRUE);
+    _inet_parse_good("0x19.00167.0003.4", "25.119.3.4", TRUE);
+    _inet_parse_good("0x19.000000167.0000003.4", "25.119.3.4", TRUE);
+    _inet_parse_good("0x0019.000000167.0000003.04", "25.119.3.4", TRUE);
+    _inet_parse_good("0x0019.0000001.000000.0x1", "25.1.0.1", TRUE);
+}
+
+/*****************************************************************************/
+
 static void
 test_garray(void)
 {
@@ -2348,7 +2472,7 @@ test_garray(void)
 static int
 _prioq_cmp(gconstpointer a, gconstpointer b)
 {
-    NM_CMP_DIRECT(GPOINTER_TO_UINT(a), GPOINTER_TO_UINT(b));
+    NM_CMP_DIRECT(*((const guint32 *) a), *((const guint32 *) b));
     return 0;
 }
 
@@ -2362,11 +2486,12 @@ static void
 test_nm_prioq(void)
 {
     nm_auto_prioq NMPrioq q = NM_PRIOQ_ZERO;
-    gpointer              data[200];
-    gpointer              data_pop[200];
+    guint32               data[200];
+    const guint32        *data_pop[200];
     guint                 data_idx[G_N_ELEMENTS(data)];
     guint                 i;
     guint                 n;
+    guint                 m;
     gpointer              p;
 
     if (nmtst_get_rand_one_case_in(10))
@@ -2379,37 +2504,67 @@ test_nm_prioq(void)
 
     g_assert(nm_prioq_size(&q) == 0);
 
-    if (nmtst_get_rand_one_case_in(10))
+    if (nmtst_get_rand_one_case_in(100))
         return;
 
     for (i = 0; i < G_N_ELEMENTS(data); i++) {
-        data[i]     = GUINT_TO_POINTER((nmtst_get_rand_uint32() % G_N_ELEMENTS(data)) + 1u);
+        data[i]     = nmtst_get_rand_uint32() % G_N_ELEMENTS(data);
         data_idx[i] = NM_PRIOQ_IDX_NULL;
     }
 
-    nm_prioq_put(&q, data[0], NULL);
+    nm_prioq_put(&q, &data[0], NULL);
     g_assert(nm_prioq_size(&q) == 1);
 
     p = nm_prioq_pop(&q);
-    g_assert(p == data[0]);
+    g_assert(p == &data[0]);
     g_assert(nm_prioq_size(&q) == 0);
 
     g_assert(!nm_prioq_pop(&q));
 
     n = nmtst_get_rand_uint32() % G_N_ELEMENTS(data);
     for (i = 0; i < n; i++)
-        nm_prioq_put(&q, data[i], &data_idx[i]);
+        nm_prioq_put(&q, &data[i], &data_idx[i]);
 
-    g_assert_cmpint(nm_prioq_size(&q), ==, n);
+    m = n;
+    for (i = 0; i < n; i++) {
+        if (!nmtst_get_rand_bool())
+            continue;
 
-    if (nmtst_get_rand_one_case_in(10))
+        data[i] = nmtst_get_rand_uint32() % G_N_ELEMENTS(data);
+        switch (nmtst_get_rand_uint32() % 4) {
+        case 0:
+            nm_prioq_reshuffle(&q, &data[i], &data_idx[i]);
+            break;
+        case 1:
+            nm_prioq_remove(&q, &data[i], nmtst_get_rand_bool() ? &data_idx[i] : NULL);
+            m--;
+            break;
+        case 2:
+            nm_prioq_update(&q, &data[i], &data_idx[i], TRUE);
+            break;
+        case 3:
+            nm_prioq_update(&q, &data[i], nmtst_get_rand_bool() ? &data_idx[i] : NULL, FALSE);
+            m--;
+            break;
+        }
+    }
+
+    g_assert_cmpint(nm_prioq_size(&q), ==, m);
+
+    if (nmtst_get_rand_one_case_in(50))
         return;
 
-    for (i = 0; i < n; i++) {
+    for (i = 0; i < m; i++) {
         data_pop[i] = nm_prioq_pop(&q);
         g_assert(data_pop[i]);
-        if (i > 0)
+        g_assert_cmpint(*data_pop[i], >=, 0);
+        g_assert_cmpint(*data_pop[i], <, G_N_ELEMENTS(data));
+        g_assert(data_pop[i] >= &data[0]);
+        g_assert(data_pop[i] < &data[n]);
+        if (i > 0) {
             g_assert(_prioq_cmp(data_pop[i - 1], data_pop[i]) <= 0);
+            g_assert_cmpint(*data_pop[i - 1], <=, *data_pop[i]);
+        }
     }
 
     g_assert(!nm_prioq_pop(&q));
@@ -2464,6 +2619,7 @@ main(int argc, char **argv)
     g_test_add_func("/general/test_path_simplify", test_path_simplify);
     g_test_add_func("/general/test_hostname_is_valid", test_hostname_is_valid);
     g_test_add_func("/general/test_inet_utils", test_inet_utils);
+    g_test_add_func("/general/test_inet_parse_ip4_legacy", test_inet_parse_ip4_legacy);
     g_test_add_func("/general/test_garray", test_garray);
     g_test_add_func("/general/test_nm_prioq", test_nm_prioq);
     g_test_add_func("/general/test_nm_random", test_nm_random);
diff --git a/src/libnm-lldp/nm-lldp-neighbor.c b/src/libnm-lldp/nm-lldp-neighbor.c
index f1e2d42e..a2a9695e 100644
--- a/src/libnm-lldp/nm-lldp-neighbor.c
+++ b/src/libnm-lldp/nm-lldp-neighbor.c
@@ -735,6 +735,7 @@ nm_lldp_neighbor_new(size_t raw_size)
 
     n->raw_size  = raw_size;
     n->ref_count = 1;
+    n->prioq_idx = NM_PRIOQ_IDX_NULL;
     return n;
 }
 
diff --git a/src/libnm-log-core/nm-logging.c b/src/libnm-log-core/nm-logging.c
index 5cce508d..0909e799 100644
--- a/src/libnm-log-core/nm-logging.c
+++ b/src/libnm-log-core/nm-logging.c
@@ -665,7 +665,7 @@ _nm_log_impl(const char *file,
     char               msg_stack[400];
     gs_free char      *msg_heap = NULL;
     const char        *msg;
-    GTimeVal           tv;
+    gint64             tv;
     int                errsv;
     const NMLogDomain *cur_log_state;
     NMLogDomain        cur_log_state_copy[_LOGL_N_REAL];
@@ -707,11 +707,26 @@ _nm_log_impl(const char *file,
 
     msg = nm_vsprintf_buf_or_alloc(fmt, fmt, msg_stack, &msg_heap, NULL);
 
-#define MESSAGE_FMT "%s%-7s [%ld.%04ld] %s"
-#define MESSAGE_ARG(prefix, tv, msg) \
-    prefix, nm_log_level_desc[level].level_str, (tv).tv_sec, ((tv).tv_usec / 100), (msg)
-
-    g_get_current_time(&tv);
+    /* We always print the level and the timestamp.
+     *
+     * Timestamps are very useful for understanding logfiles. While journalctl
+     * might record the timestamp, it is not present in plain `journalctl` output.
+     * Users who report a bug would simply send us the `journalctl` output and
+     * requesting an output with timestamps (even if it's stored somewhere inside
+     * journald) is not workable.
+     *
+     * We print the level, because this too, it's to quickly identify the severity
+     * of a message.
+     *
+     * We also do this for all messages (for all levels), because then the logging
+     * lines are formatted and aligned in a consistent way, which aids reading the
+     * logs. */
+#define MESSAGE_FMT "%s%-7s [%" G_GINT64_FORMAT ".%04d] %s"
+#define MESSAGE_ARG(prefix, tv, msg)                                            \
+    prefix, nm_log_level_desc[level].level_str, ((tv) / NM_UTILS_USEC_PER_SEC), \
+        ((int) ((((tv) % NM_UTILS_USEC_PER_SEC)) / ((gint64) 100))), (msg)
+
+    tv = g_get_real_time();
 
     if (g->debug_stderr)
         g_printerr(MESSAGE_FMT "\n", MESSAGE_ARG(g->prefix, tv, msg));
diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c
index 484087b9..99eab9c7 100644
--- a/src/libnm-platform/nm-linux-platform.c
+++ b/src/libnm-platform/nm-linux-platform.c
@@ -115,7 +115,9 @@ typedef enum _nm_packed {
 #define IFLA_CARRIER       33
 #define IFLA_PHYS_PORT_ID  34
 #define IFLA_LINK_NETNSID  37
-#define __IFLA_MAX         39
+#define IFLA_GSO_MAX_SEGS  40
+#define IFLA_GSO_MAX_SIZE  41
+#define IFLA_GRO_MAX_SIZE  58
 
 #define IFLA_INET6_TOKEN         7
 #define IFLA_INET6_ADDR_GEN_MODE 8
@@ -180,6 +182,9 @@ G_STATIC_ASSERT(RTA_MAX == (__RTA_MAX - 1));
 #define IFLA_BOND_SLAVE_PRIO 9
 
 #define IFLA_BOND_PEER_NOTIF_DELAY 28
+#define IFLA_BOND_AD_LACP_ACTIVE   29
+#define IFLA_BOND_MISSED_MAX       30
+#define IFLA_BOND_NS_IP6_TARGET    31
 
 #undef IFLA_BOND_MAX
 
@@ -325,6 +330,10 @@ struct _ifla_vf_vlan_info {
 
 /*****************************************************************************/
 
+#define RESYNC_RETRIES 50
+
+/*****************************************************************************/
+
 typedef struct {
     guint16 family_id;
 } GenlFamilyData;
@@ -452,7 +461,14 @@ typedef enum _nm_packed {
     WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN = 0,
     WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK,
     WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN,
+
+    /* The receive buffer of the netlink socket has a large, but limited size.
+     * It can fill up, and we lose messages. When that happens, we may lose a
+     * response that we were waiting for. This error number indicates that we
+     * don't know the response due to a resync. We probably should retry the
+     * request. */
     WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC,
+
     WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL,
     WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT,
     WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING,
@@ -467,7 +483,7 @@ typedef enum _nm_packed {
 
 typedef struct {
     WaitForNlResponseResult *out_seq_result;
-    char                   **out_errmsg;
+    char                   **out_extack_msg;
     union {
         int        *out_refresh_all_in_progress;
         NMPObject **out_route_get;
@@ -484,9 +500,6 @@ typedef struct {
 typedef struct {
     guint32 nlh_seq_next;
     guint32 nlh_seq_last_seen;
-#if NM_MORE_LOGGING
-    guint32 nlh_seq_last_handled;
-#endif
 } NetlinkProtocolPrivData;
 
 typedef struct {
@@ -711,7 +724,7 @@ wait_for_nl_response_to_nmerr(WaitForNlResponseResult seq_result)
 
 static const char *
 wait_for_nl_response_to_string(WaitForNlResponseResult seq_result,
-                               const char             *errmsg,
+                               const char             *extack_msg,
                                char                   *buf,
                                gsize                   buf_size)
 {
@@ -737,8 +750,8 @@ wait_for_nl_response_to_string(WaitForNlResponseResult seq_result,
                              "failure %d (%s%s%s)",
                              -((int) seq_result),
                              nm_strerror_native(-((int) seq_result)),
-                             errmsg ? " - " : "",
-                             errmsg ?: "");
+                             extack_msg ? " - " : "",
+                             extack_msg ?: "");
         } else
             nm_strbuf_append(&buf, &buf_size, "internal failure %d", (int) seq_result);
         break;
@@ -1609,6 +1622,9 @@ _parse_lnk_bond(const char *kind, struct nlattr *info_data)
         [IFLA_BOND_AD_ACTOR_SYSTEM]   = {.minlen = sizeof(NMEtherAddr)},
         [IFLA_BOND_TLB_DYNAMIC_LB]    = {.type = NLA_U8},
         [IFLA_BOND_PEER_NOTIF_DELAY]  = {.type = NLA_U32},
+        [IFLA_BOND_MISSED_MAX]        = {.type = NLA_U8},
+        [IFLA_BOND_AD_LACP_ACTIVE]    = {.type = NLA_U8},
+        [IFLA_BOND_NS_IP6_TARGET]     = {.type = NLA_NESTED},
     };
     NMPlatformLnkBond *props;
     struct nlattr     *tb[G_N_ELEMENTS(policy)];
@@ -1663,6 +1679,19 @@ _parse_lnk_bond(const char *kind, struct nlattr *info_data)
             props->arp_ip_target[props->arp_ip_targets_num++] = nla_get_u32(attr);
         }
     }
+    if (tb[IFLA_BOND_NS_IP6_TARGET]) {
+        struct nlattr *attr;
+        int            rem;
+
+        nla_for_each_nested (attr, tb[IFLA_BOND_NS_IP6_TARGET], rem) {
+            if (props->ns_ip6_targets_num > NM_BOND_MAX_ARP_TARGETS - 1)
+                break;
+            if (nla_len(attr) < sizeof(struct in6_addr))
+                break;
+
+            props->ns_ip6_target[props->ns_ip6_targets_num++] = nla_get_in6_addr(attr);
+        }
+    }
     if (tb[IFLA_BOND_ARP_VALIDATE])
         props->arp_validate = nla_get_u32(tb[IFLA_BOND_ARP_VALIDATE]);
     if (tb[IFLA_BOND_ARP_ALL_TARGETS])
@@ -1683,6 +1712,8 @@ _parse_lnk_bond(const char *kind, struct nlattr *info_data)
         props->num_grat_arp = nla_get_u8(tb[IFLA_BOND_NUM_PEER_NOTIF]);
     if (tb[IFLA_BOND_ALL_SLAVES_ACTIVE])
         props->all_ports_active = nla_get_u8(tb[IFLA_BOND_ALL_SLAVES_ACTIVE]);
+    if (tb[IFLA_BOND_MISSED_MAX])
+        props->arp_missed_max = nla_get_u8(tb[IFLA_BOND_MISSED_MAX]);
     if (tb[IFLA_BOND_MIN_LINKS])
         props->min_links = nla_get_u32(tb[IFLA_BOND_MIN_LINKS]);
     if (tb[IFLA_BOND_LP_INTERVAL])
@@ -1691,6 +1722,10 @@ _parse_lnk_bond(const char *kind, struct nlattr *info_data)
         props->packets_per_port = nla_get_u32(tb[IFLA_BOND_PACKETS_PER_SLAVE]);
     if (tb[IFLA_BOND_AD_LACP_RATE])
         props->lacp_rate = nla_get_u8(tb[IFLA_BOND_AD_LACP_RATE]);
+    if (tb[IFLA_BOND_AD_LACP_ACTIVE]) {
+        props->lacp_active     = nla_get_u8(tb[IFLA_BOND_AD_LACP_ACTIVE]);
+        props->lacp_active_has = TRUE;
+    }
     if (tb[IFLA_BOND_AD_SELECT])
         props->ad_select = nla_get_u8(tb[IFLA_BOND_AD_SELECT]);
     if (tb[IFLA_BOND_AD_ACTOR_SYS_PRIO])
@@ -3265,6 +3300,9 @@ _new_from_nl_link(NMPlatform            *platform,
         [IFLA_IFNAME]        = {.type = NLA_STRING, .maxlen = IFNAMSIZ},
         [IFLA_MTU]           = {.type = NLA_U32},
         [IFLA_TXQLEN]        = {.type = NLA_U32},
+        [IFLA_GSO_MAX_SIZE]  = {.type = NLA_U32},
+        [IFLA_GSO_MAX_SEGS]  = {.type = NLA_U32},
+        [IFLA_GRO_MAX_SIZE]  = {.type = NLA_U32},
         [IFLA_LINK]          = {.type = NLA_U32},
         [IFLA_WEIGHT]        = {.type = NLA_U32},
         [IFLA_MASTER]        = {.type = NLA_U32},
@@ -3409,6 +3447,15 @@ _new_from_nl_link(NMPlatform            *platform,
         }
     }
 
+    if (tb[IFLA_TXQLEN])
+        obj->link.link_props.tx_queue_length = nla_get_u32(tb[IFLA_TXQLEN]);
+    if (tb[IFLA_GSO_MAX_SIZE])
+        obj->link.link_props.gso_max_size = nla_get_u32(tb[IFLA_GSO_MAX_SIZE]);
+    if (tb[IFLA_GSO_MAX_SEGS])
+        obj->link.link_props.gso_max_segments = nla_get_u32(tb[IFLA_GSO_MAX_SEGS]);
+    if (tb[IFLA_GRO_MAX_SIZE])
+        obj->link.link_props.gro_max_size = nla_get_u32(tb[IFLA_GRO_MAX_SIZE]);
+
     if (tb[IFLA_STATS64]) {
         const char *stats = nla_data(tb[IFLA_STATS64]);
 
@@ -4568,15 +4615,15 @@ _new_from_nl_tfilter(NMPlatform *platform, const struct nlmsghdr *nlh, gboolean
 
 /**
  * nmp_object_new_from_nl:
- * @platform: (allow-none): for creating certain objects, the constructor wants to check
+ * @platform: (nullable): for creating certain objects, the constructor wants to check
  *   sysfs. For this the platform instance is needed. If missing, the object might not
  *   be correctly detected.
- * @cache: (allow-none): for certain objects, the netlink message doesn't contain all the information.
+ * @cache: (nullable): for certain objects, the netlink message doesn't contain all the information.
  *   If a cache is given, the object is completed with information from the cache.
  * @nlh: the netlink message header
  * @id_only: whether only to create an empty object with only the ID fields set.
  *
- * Returns: %NULL or a newly created NMPObject instance.
+ * Returns: (nullable): %NULL or a newly created NMPObject instance.
  **/
 static NMPObject *
 nmp_object_new_from_nl(NMPlatform               *platform,
@@ -4735,6 +4782,17 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo
             nla_nest_end(msg, targets);
         }
 
+        if (props->ns_ip6_targets_num > 0) {
+            targets = nla_nest_start(msg, IFLA_BOND_NS_IP6_TARGET);
+            if (!targets)
+                goto nla_put_failure;
+
+            for (i = 0; i < props->ns_ip6_targets_num; i++)
+                NLA_PUT(msg, i, sizeof(struct in6_addr), &props->ns_ip6_target[i]);
+
+            nla_nest_end(msg, targets);
+        }
+
         if (props->arp_all_targets)
             NLA_PUT_U32(msg, IFLA_BOND_ARP_ALL_TARGETS, props->arp_all_targets);
         if (props->arp_interval)
@@ -4770,6 +4828,8 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo
                     &props->ad_actor_system);
         if (props->ad_select)
             NLA_PUT_U8(msg, IFLA_BOND_AD_SELECT, props->ad_select);
+        if (props->arp_missed_max)
+            NLA_PUT_U8(msg, IFLA_BOND_MISSED_MAX, props->arp_missed_max);
 
         NLA_PUT_U8(msg, IFLA_BOND_ALL_SLAVES_ACTIVE, props->all_ports_active);
 
@@ -4777,6 +4837,8 @@ _nl_msg_new_link_set_linkinfo(struct nl_msg *msg, NMLinkType link_type, gconstpo
             NLA_PUT_U8(msg, IFLA_BOND_FAIL_OVER_MAC, props->fail_over_mac);
         if (props->lacp_rate)
             NLA_PUT_U8(msg, IFLA_BOND_AD_LACP_RATE, props->lacp_rate);
+        if (props->lacp_active_has)
+            NLA_PUT_U8(msg, IFLA_BOND_AD_LACP_ACTIVE, props->lacp_active);
         if (props->num_grat_arp)
             NLA_PUT_U8(msg, IFLA_BOND_NUM_PEER_NOTIF, props->num_grat_arp);
 
@@ -6690,7 +6752,7 @@ delayed_action_wait_for_response_complete(NMPlatform             *platform,
 
     nm_assert(NM_FLAGS_ANY(priv->delayed_action.flags, ACTION_TYPE));
     nm_assert(idx < priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len);
-    nm_assert(seq_result);
+    nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
 
     data = delayed_action_get_list_wait_for_resonse(priv, netlink_protocol, idx);
 
@@ -7041,7 +7103,7 @@ delayed_action_schedule_WAIT_FOR_RESPONSE(NMPlatform                        *pla
                                           NMPNetlinkProtocol                 netlink_protocol,
                                           guint32                            seq_number,
                                           WaitForNlResponseResult           *out_seq_result,
-                                          char                             **out_errmsg,
+                                          char                             **out_extack_msg,
                                           DelayedActionWaitForNlResponseType response_type,
                                           gpointer                           response_out_data)
 {
@@ -7050,11 +7112,13 @@ delayed_action_schedule_WAIT_FOR_RESPONSE(NMPlatform                        *pla
         .timeout_abs_nsec =
             nm_utils_get_monotonic_timestamp_nsec() + (200 * (NM_UTILS_NSEC_PER_SEC / 1000)),
         .out_seq_result    = out_seq_result,
-        .out_errmsg        = out_errmsg,
+        .out_extack_msg    = out_extack_msg,
         .response_type     = response_type,
         .response.out_data = response_out_data,
     };
 
+    nm_assert(!out_seq_result || *out_seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
+
     delayed_action_schedule(
         platform,
         nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response,
@@ -7409,7 +7473,7 @@ static int
 _nl_send_nlmsghdr(NMPlatform                        *platform,
                   struct nlmsghdr                   *nlhdr,
                   WaitForNlResponseResult           *out_seq_result,
-                  char                             **out_errmsg,
+                  char                             **out_extack_msg,
                   DelayedActionWaitForNlResponseType response_type,
                   gpointer                           response_out_data)
 {
@@ -7419,6 +7483,8 @@ _nl_send_nlmsghdr(NMPlatform                        *platform,
 
     nm_assert(nlhdr);
 
+    nm_assert(out_seq_result && *out_seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
+
     seq              = _nlh_seq_next_get(priv, NMP_NETLINK_ROUTE);
     nlhdr->nlmsg_seq = seq;
 
@@ -7446,7 +7512,7 @@ again:
             errsv = errno;
             if (errsv == EINTR && try_count++ < 100)
                 goto again;
-            _LOGD("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)",
+            _LOGI("netlink: nl-send-nlmsghdr: failed sending message: %s (%d)",
                   nm_strerror_native(errsv),
                   errsv);
             return -nm_errno_from_native(errsv);
@@ -7457,7 +7523,7 @@ again:
                                               NMP_NETLINK_ROUTE,
                                               seq,
                                               out_seq_result,
-                                              out_errmsg,
+                                              out_extack_msg,
                                               response_type,
                                               response_out_data);
     return 0;
@@ -7468,7 +7534,7 @@ _netlink_send_nlmsg(NMPlatform                        *platform,
                     NMPNetlinkProtocol                 netlink_protocol,
                     struct nl_msg                     *nlmsg,
                     WaitForNlResponseResult           *out_seq_result,
-                    char                             **out_errmsg,
+                    char                             **out_extack_msg,
                     DelayedActionWaitForNlResponseType response_type,
                     gpointer                           response_out_data)
 {
@@ -7477,13 +7543,15 @@ _netlink_send_nlmsg(NMPlatform                        *platform,
     guint32                 seq;
     int                     nle;
 
+    nm_assert(!out_seq_result || *out_seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
+
     nlhdr            = nlmsg_hdr(nlmsg);
     seq              = _nlh_seq_next_get(priv, netlink_protocol);
     nlhdr->nlmsg_seq = seq;
 
     nle = nl_send_auto(priv->sk_x[netlink_protocol], nlmsg);
     if (nle < 0) {
-        _LOGD("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nm_strerror(nle), nle);
+        _LOGI("netlink: nl-send-nlmsg: failed sending message: %s (%d)", nm_strerror(nle), nle);
         return nle;
     }
 
@@ -7491,7 +7559,7 @@ _netlink_send_nlmsg(NMPlatform                        *platform,
                                               netlink_protocol,
                                               seq,
                                               out_seq_result,
-                                              out_errmsg,
+                                              out_extack_msg,
                                               response_type,
                                               response_out_data);
     return 0;
@@ -7501,13 +7569,13 @@ static int
 _netlink_send_nlmsg_rtnl(NMPlatform              *platform,
                          struct nl_msg           *nlmsg,
                          WaitForNlResponseResult *out_seq_result,
-                         char                   **out_errmsg)
+                         char                   **out_extack_msg)
 {
     return _netlink_send_nlmsg(platform,
                                NMP_NETLINK_ROUTE,
                                nlmsg,
                                out_seq_result,
-                               out_errmsg,
+                               out_extack_msg,
                                DELAYED_ACTION_RESPONSE_TYPE_VOID,
                                NULL);
 }
@@ -7778,7 +7846,7 @@ event_seq_check(NMPlatform             *platform,
                 NMPNetlinkProtocol      netlink_protocol,
                 guint32                 seq_number,
                 WaitForNlResponseResult seq_result,
-                const char             *msg)
+                const char             *extack_msg)
 {
     NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform);
     guint                   i;
@@ -7789,7 +7857,7 @@ event_seq_check(NMPlatform             *platform,
     if (!NM_FLAGS_ANY(
             priv->delayed_action.flags,
             nmp_netlink_protocol_info(netlink_protocol)->delayed_action_type_wait_for_response))
-        goto out;
+        return;
 
     nm_assert(priv->delayed_action.list_wait_for_response_x[netlink_protocol]->len > 0);
 
@@ -7797,30 +7865,23 @@ event_seq_check(NMPlatform             *platform,
         DelayedActionWaitForNlResponseData *data =
             delayed_action_get_list_wait_for_resonse(priv, netlink_protocol, i);
 
-        if (data->seq_number == seq_number) {
-            /* We potentially receive many parts partial responses for the same sequence number.
-                 * Thus, we only remember the result, and collect it later. */
-            if (data->seq_result < 0) {
-                /* we already saw an error for this sequence number.
-                     * Preserve it. */
-            } else if (seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
-                       || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
-                data->seq_result = seq_result;
-            if (data->out_errmsg && !*data->out_errmsg)
-                *data->out_errmsg = g_strdup(msg);
-            return;
-        }
-    }
+        if (data->seq_number != seq_number)
+            continue;
 
-out:
+        /* We potentially receive many parts partial responses for the same sequence number.
+         * Thus, we only remember the result, and collect it later. */
+        if (data->seq_result < 0) {
+            /* we already saw an error for this sequence number.
+             * Preserve it. */
+        } else if (seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
+                   || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
+            data->seq_result = seq_result;
 
-#if NM_MORE_LOGGING
-    if (seq_number != priv->proto_data_x[netlink_protocol].nlh_seq_last_handled)
-        _LOGt("netlink: recvmsg: unwaited sequence number %u", seq_number);
-    priv->proto_data_x[netlink_protocol].nlh_seq_last_handled = seq_number;
-#else
-    (void) 0;
-#endif
+        if (extack_msg && data->out_extack_msg && !*data->out_extack_msg)
+            *data->out_extack_msg = g_strdup(extack_msg);
+
+        return;
+    }
 }
 
 static void
@@ -8056,35 +8117,41 @@ do_add_link_with_lookup(NMPlatform            *platform,
                         struct nl_msg         *nlmsg,
                         const NMPlatformLink **out_link)
 {
-    const NMPObject        *obj        = NULL;
-    WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char           *errmsg     = NULL;
+    const NMPObject        *obj = NULL;
+    WaitForNlResponseResult seq_result;
+    gs_free char           *extack_msg = NULL;
     int                     nle;
     char                    s_buf[256];
-    NMPCache               *cache = nm_platform_get_cache(platform);
+    NMPCache               *cache     = nm_platform_get_cache(platform);
+    int                     try_count = 0;
 
     event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &errmsg);
-    if (nle < 0) {
-        _LOGE("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
-              name,
-              nm_link_type_to_string(link_type),
-              nm_strerror(nle),
-              -nle);
-        NM_SET_OUT(out_link, NULL);
-        return nle;
-    }
+    do {
+        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+        nle        = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &extack_msg);
+        if (nle < 0) {
+            _LOGE("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
+                  name,
+                  nm_link_type_to_string(link_type),
+                  nm_strerror(nle),
+                  -nle);
+            NM_SET_OUT(out_link, NULL);
+            return nle;
+        }
 
-    delayed_action_handle_all(platform);
+        delayed_action_handle_all(platform);
 
-    nm_assert(seq_result);
+        nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
 
-    _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
-           "do-add-link[%s/%s]: %s",
-           name,
-           nm_link_type_to_string(link_type),
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
+        _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
+               "do-add-link[%s/%s]: %s",
+               name,
+               nm_link_type_to_string(link_type),
+               wait_for_nl_response_to_string(seq_result, extack_msg, s_buf, sizeof(s_buf)));
+
+    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+             && ++try_count < RESYNC_RETRIES);
 
     if (out_link) {
         obj       = nmp_cache_lookup_link_full(cache, 0, name, FALSE, link_type, NULL, NULL);
@@ -8098,14 +8165,17 @@ static int
 do_add_addrroute(NMPlatform      *platform,
                  const NMPObject *obj_id,
                  struct nl_msg   *nlmsg,
-                 gboolean         suppress_netlink_failure)
+                 gboolean         suppress_netlink_failure,
+                 char           **out_extack_msg)
 {
     char                    sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE];
-    WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char           *errmsg     = NULL;
+    WaitForNlResponseResult seq_result;
+    gs_free char           *extack_msg = NULL;
     int                     nle;
     char                    s_buf[256];
+    int                     try_count = 0;
 
+    nm_assert(!out_extack_msg || !*out_extack_msg);
     nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id),
                         NMP_OBJECT_TYPE_IP4_ADDRESS,
                         NMP_OBJECT_TYPE_IP6_ADDRESS,
@@ -8114,28 +8184,34 @@ do_add_addrroute(NMPlatform      *platform,
 
     event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &errmsg);
-    if (nle < 0) {
-        _LOGE("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
-              NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
-              nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
-              nm_strerror(nle),
-              -nle);
-        return -NME_PL_NETLINK;
-    }
+    do {
+        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+        nle        = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &extack_msg);
+        if (nle < 0) {
+            _LOGE("do-add-%s[%s]: failure sending netlink request \"%s\" (%d)",
+                  NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
+                  nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
+                  nm_strerror(nle),
+                  -nle);
+            NM_SET_OUT(out_extack_msg, g_steal_pointer(&extack_msg));
+            return -NME_PL_NETLINK;
+        }
 
-    delayed_action_handle_all(platform);
+        delayed_action_handle_all(platform);
+
+        nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
 
-    nm_assert(seq_result);
+        _NMLOG((seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+                || (suppress_netlink_failure && seq_result < 0))
+                   ? LOGL_DEBUG
+                   : LOGL_WARN,
+               "do-add-%s[%s]: %s",
+               NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
+               nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
+               wait_for_nl_response_to_string(seq_result, extack_msg, s_buf, sizeof(s_buf)));
 
-    _NMLOG((seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
-            || (suppress_netlink_failure && seq_result < 0))
-               ? LOGL_DEBUG
-               : LOGL_WARN,
-           "do-add-%s[%s]: %s",
-           NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
-           nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
+    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+             && ++try_count < RESYNC_RETRIES);
 
     if (NMP_OBJECT_GET_TYPE(obj_id) == NMP_OBJECT_TYPE_IP6_ADDRESS) {
         /* In rare cases, the object is not yet ready as we received the ACK from
@@ -8149,6 +8225,7 @@ do_add_addrroute(NMPlatform      *platform,
             do_request_one_type_by_needle_object(platform, obj_id);
     }
 
+    NM_SET_OUT(out_extack_msg, g_steal_pointer(&extack_msg));
     return wait_for_nl_response_to_nmerr(seq_result);
 }
 
@@ -8156,54 +8233,60 @@ static gboolean
 do_delete_object(NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
     char                    sbuf1[NM_UTILS_TO_STRING_BUFFER_SIZE];
-    WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char           *errmsg     = NULL;
+    WaitForNlResponseResult seq_result;
+    gs_free char           *extack_msg = NULL;
     int                     nle;
     char                    s_buf[256];
     gboolean                success;
     const char             *log_detail = "";
+    int                     try_count  = 0;
 
     event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &errmsg);
-    if (nle < 0) {
-        _LOGE("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
-              NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
-              nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
-              nm_strerror(nle),
-              -nle);
-        return FALSE;
-    }
+    do {
+        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+        nle        = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &extack_msg);
+        if (nle < 0) {
+            _LOGE("do-delete-%s[%s]: failure sending netlink request \"%s\" (%d)",
+                  NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
+                  nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
+                  nm_strerror(nle),
+                  -nle);
+            return FALSE;
+        }
 
-    delayed_action_handle_all(platform);
+        delayed_action_handle_all(platform);
 
-    nm_assert(seq_result);
+        nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
+
+        success = TRUE;
+        if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
+            /* ok */
+        } else if (NM_IN_SET(-((int) seq_result), ESRCH, ENOENT))
+            log_detail = ", meaning the object was already removed";
+        else if (NM_IN_SET(-((int) seq_result), ENXIO)
+                 && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), NMP_OBJECT_TYPE_IP6_ADDRESS)) {
+            /* On RHEL7 kernel, deleting a non existing address fails with ENXIO */
+            log_detail = ", meaning the address was already removed";
+        } else if (NM_IN_SET(-((int) seq_result), ENODEV)) {
+            log_detail = ", meaning the device was already removed";
+        } else if (NM_IN_SET(-((int) seq_result), EADDRNOTAVAIL)
+                   && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id),
+                                NMP_OBJECT_TYPE_IP4_ADDRESS,
+                                NMP_OBJECT_TYPE_IP6_ADDRESS))
+            log_detail = ", meaning the address was already removed";
+        else
+            success = FALSE;
 
-    success = TRUE;
-    if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
-        /* ok */
-    } else if (NM_IN_SET(-((int) seq_result), ESRCH, ENOENT))
-        log_detail = ", meaning the object was already removed";
-    else if (NM_IN_SET(-((int) seq_result), ENXIO)
-             && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id), NMP_OBJECT_TYPE_IP6_ADDRESS)) {
-        /* On RHEL7 kernel, deleting a non existing address fails with ENXIO */
-        log_detail = ", meaning the address was already removed";
-    } else if (NM_IN_SET(-((int) seq_result), ENODEV)) {
-        log_detail = ", meaning the device was already removed";
-    } else if (NM_IN_SET(-((int) seq_result), EADDRNOTAVAIL)
-               && NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id),
-                            NMP_OBJECT_TYPE_IP4_ADDRESS,
-                            NMP_OBJECT_TYPE_IP6_ADDRESS))
-        log_detail = ", meaning the address was already removed";
-    else
-        success = FALSE;
+        _NMLOG(success ? LOGL_DEBUG : LOGL_WARN,
+               "do-delete-%s[%s]: %s%s",
+               NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
+               nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
+               wait_for_nl_response_to_string(seq_result, extack_msg, s_buf, sizeof(s_buf)),
+               log_detail);
 
-    _NMLOG(success ? LOGL_DEBUG : LOGL_WARN,
-           "do-delete-%s[%s]: %s%s",
-           NMP_OBJECT_GET_CLASS(obj_id)->obj_type_name,
-           nmp_object_to_string(obj_id, NMP_OBJECT_TO_STRING_ID, sbuf1, sizeof(sbuf1)),
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)),
-           log_detail);
+    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+             && ++try_count < RESYNC_RETRIES);
 
     if (NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_id),
                   NMP_OBJECT_TYPE_IP6_ADDRESS,
@@ -8232,14 +8315,15 @@ do_change_link(NMPlatform           *platform,
 {
     nm_auto_pop_netns NMPNetns *netns = NULL;
     int                         nle;
-    WaitForNlResponseResult     seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char               *errmsg     = NULL;
+    WaitForNlResponseResult     seq_result;
+    gs_free char               *extack_msg = NULL;
     char                        s_buf[256];
     int                         result;
     NMLogLevel                  log_level;
     const char                 *log_detail;
     gs_free char               *log_detail_free = NULL;
     const NMPObject            *obj_cache;
+    int                         try_count = 0;
 
     if (!nm_platform_netns_push(platform, &netns)) {
         log_level  = LOGL_ERR;
@@ -8249,12 +8333,13 @@ do_change_link(NMPlatform           *platform,
     }
 
 retry:
+    seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
     result     = -NME_UNSPEC;
     log_level  = LOGL_WARN;
     log_detail = "";
     nm_clear_g_free(&log_detail_free);
 
-    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &errmsg);
+    nle = _netlink_send_nlmsg_rtnl(platform, nlmsg, &seq_result, &extack_msg);
     if (nle < 0) {
         log_level = LOGL_ERR;
         log_detail_free =
@@ -8269,7 +8354,7 @@ retry:
 
     delayed_action_handle_all(platform);
 
-    nm_assert(seq_result);
+    nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
 
     if (NM_IN_SET(seq_result, WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK, -EEXIST, -EADDRINUSE)) {
         log_level = LOGL_DEBUG;
@@ -8314,10 +8399,10 @@ out:
     _NMLOG(log_level,
            "do-change-link[%d]: %s%s",
            ifindex,
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)),
+           wait_for_nl_response_to_string(seq_result, extack_msg, s_buf, sizeof(s_buf)),
            log_detail);
 
-    if (result == -EAGAIN)
+    if (result == -EAGAIN && ++try_count < RESYNC_RETRIES)
         goto retry;
 
     return result;
@@ -8404,8 +8489,10 @@ link_delete(NMPlatform *platform, int ifindex)
 static gboolean
 link_change(NMPlatform                   *platform,
             int                           ifindex,
+            NMPlatformLinkProps          *props,
             NMPortKind                    port_kind,
-            const NMPlatformLinkPortData *port_data)
+            const NMPlatformLinkPortData *port_data,
+            NMPlatformLinkChangeFlags     flags)
 {
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
     struct nlattr               *nl_info;
@@ -8415,6 +8502,15 @@ link_change(NMPlatform                   *platform,
     if (!nlmsg)
         return FALSE;
 
+    if (flags & NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH)
+        NLA_PUT_U32(nlmsg, IFLA_TXQLEN, props->tx_queue_length);
+    if (flags & NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE)
+        NLA_PUT_U32(nlmsg, IFLA_GSO_MAX_SIZE, props->gso_max_size);
+    if (flags & NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS)
+        NLA_PUT_U32(nlmsg, IFLA_GSO_MAX_SEGS, props->gso_max_segments);
+    if (flags & NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE)
+        NLA_PUT_U32(nlmsg, IFLA_GRO_MAX_SIZE, props->gro_max_size);
+
     switch (port_kind) {
     case NM_PORT_KIND_BOND:
 
@@ -8573,10 +8669,10 @@ link_supports_sriov(NMPlatform *platform, int ifindex)
     if (dirfd < 0)
         return FALSE;
 
-    num =
-        nm_platform_sysctl_get_int32(platform,
-                                     NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"),
-                                     -1);
+    num = nm_platform_sysctl_get_int32(
+        platform,
+        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"),
+        -1);
 
     return num != -1;
 }
@@ -8710,7 +8806,7 @@ link_set_sriov_params_async(NMPlatform             *platform,
 
     total = nm_platform_sysctl_get_int_checked(
         platform,
-        NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_totalvfs"),
+        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_totalvfs"),
         10,
         0,
         G_MAXUINT,
@@ -8728,14 +8824,14 @@ link_set_sriov_params_async(NMPlatform             *platform,
      */
     current_num = nm_platform_sysctl_get_int_checked(
         platform,
-        NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"),
+        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"),
         10,
         0,
         G_MAXUINT,
         -1);
     current_autoprobe = nm_platform_sysctl_get_int_checked(
         platform,
-        NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"),
+        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_drivers_autoprobe"),
         10,
         0,
         1,
@@ -8755,7 +8851,7 @@ link_set_sriov_params_async(NMPlatform             *platform,
         && current_autoprobe != autoprobe
         && !nm_platform_sysctl_set(
             platform,
-            NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_drivers_autoprobe"),
+            NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_drivers_autoprobe"),
             nm_sprintf_buf(buf, "%d", (int) autoprobe))) {
         g_set_error(&error,
                     NM_UTILS_ERROR,
@@ -8777,7 +8873,7 @@ link_set_sriov_params_async(NMPlatform             *platform,
     values[i++] = NULL;
 
     sysctl_set_async(platform,
-                     NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "device/sriov_numvfs"),
+                     NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"),
                      values,
                      callback,
                      data,
@@ -8963,6 +9059,47 @@ nla_put_failure:
     g_return_val_if_reached(FALSE);
 }
 
+static gboolean
+link_set_bridge_info(NMPlatform                            *platform,
+                     int                                    ifindex,
+                     const NMPlatformLinkSetBridgeInfoData *bridge_info)
+{
+    nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+    struct nlattr               *info;
+    struct nlattr               *data;
+    const char                  *kind;
+
+    nlmsg = _nl_msg_new_link(RTM_NEWLINK, 0, ifindex, NULL);
+    if (!nlmsg)
+        g_return_val_if_reached(-NME_BUG);
+
+    if (!(info = nla_nest_start(nlmsg, IFLA_LINKINFO)))
+        goto nla_put_failure;
+
+    kind = nm_link_type_to_rtnl_type_string(NM_LINK_TYPE_BRIDGE);
+    if (!kind)
+        goto nla_put_failure;
+
+    NLA_PUT_STRING(nlmsg, IFLA_INFO_KIND, kind);
+
+    if (!(data = nla_nest_start(nlmsg, IFLA_INFO_DATA)))
+        goto nla_put_failure;
+
+    if (bridge_info->vlan_filtering_has)
+        NLA_PUT_U8(nlmsg, IFLA_BR_VLAN_FILTERING, bridge_info->vlan_filtering_val);
+
+    if (bridge_info->vlan_default_pvid_has)
+        NLA_PUT_U16(nlmsg, IFLA_BR_VLAN_DEFAULT_PVID, bridge_info->vlan_default_pvid_val);
+
+    nla_nest_end(nlmsg, data);
+    nla_nest_end(nlmsg, info);
+
+    return (do_change_link(platform, CHANGE_LINK_TYPE_UNSPEC, ifindex, nlmsg, NULL) >= 0);
+
+nla_put_failure:
+    g_return_val_if_reached(FALSE);
+}
+
 static char *
 link_get_physical_port_id(NMPlatform *platform, int ifindex)
 {
@@ -8972,7 +9109,7 @@ link_get_physical_port_id(NMPlatform *platform, int ifindex)
     dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname_verified);
     if (dirfd < 0)
         return NULL;
-    return sysctl_get(platform, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "phys_port_id"));
+    return sysctl_get(platform, NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_verified, "phys_port_id"));
 }
 
 static guint
@@ -8986,7 +9123,7 @@ link_get_dev_id(NMPlatform *platform, int ifindex)
         return 0;
     return nm_platform_sysctl_get_int_checked(
         platform,
-        NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "dev_id"),
+        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_verified, "dev_id"),
         16,
         0,
         G_MAXUINT16,
@@ -9236,12 +9373,12 @@ _infiniband_partition_action(NMPlatform            *platform,
     if (action == INFINIBAND_ACTION_CREATE_CHILD)
         success =
             nm_platform_sysctl_set(platform,
-                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_parent, "create_child"),
+                                   NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_parent, "create_child"),
                                    id);
     else
         success =
             nm_platform_sysctl_set(platform,
-                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_parent, "delete_child"),
+                                   NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_parent, "delete_child"),
                                    id);
 
     if (!success) {
@@ -9250,7 +9387,7 @@ _infiniband_partition_action(NMPlatform            *platform,
         return FALSE;
     }
 
-    nmp_utils_new_infiniband_name(name, ifname_parent, p_key);
+    nm_net_devname_infiniband(name, ifname_parent, p_key);
     do_request_link(platform, 0, name);
 
     if (action == INFINIBAND_ACTION_DELETE_CHILD)
@@ -9591,7 +9728,8 @@ ip4_address_add(NMPlatform *platform,
                 guint32     lifetime,
                 guint32     preferred,
                 guint32     flags,
-                const char *label)
+                const char *label,
+                char      **out_extack_msg)
 {
     NMPObject                    obj_id;
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -9611,7 +9749,7 @@ ip4_address_add(NMPlatform *platform,
                                 label);
 
     nmp_object_stackinit_id_ip4_address(&obj_id, ifindex, addr, plen, peer_addr);
-    return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE) >= 0);
+    return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE, out_extack_msg) >= 0);
 }
 
 static gboolean
@@ -9622,7 +9760,8 @@ ip6_address_add(NMPlatform     *platform,
                 struct in6_addr peer_addr,
                 guint32         lifetime,
                 guint32         preferred,
-                guint32         flags)
+                guint32         flags,
+                char          **out_extack_msg)
 {
     NMPObject                    obj_id;
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
@@ -9642,7 +9781,7 @@ ip6_address_add(NMPlatform     *platform,
                                 NULL);
 
     nmp_object_stackinit_id_ip6_address(&obj_id, ifindex, &addr);
-    return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE) >= 0);
+    return (do_add_addrroute(platform, &obj_id, nlmsg, FALSE, out_extack_msg) >= 0);
 }
 
 static gboolean
@@ -9704,7 +9843,7 @@ ip6_address_delete(NMPlatform *platform, int ifindex, struct in6_addr addr, guin
 /*****************************************************************************/
 
 static int
-ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack)
+ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack, char **out_extack_msg)
 {
     nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
@@ -9714,7 +9853,8 @@ ip_route_add(NMPlatform *platform, NMPNlmFlags flags, NMPObject *obj_stack)
     return do_add_addrroute(platform,
                             obj_stack,
                             nlmsg,
-                            NM_FLAGS_HAS(flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE));
+                            NM_FLAGS_HAS(flags, NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE),
+                            out_extack_msg);
 }
 
 static gboolean
@@ -9815,9 +9955,10 @@ ip_route_get(NMPlatform   *platform,
 
         delayed_action_handle_all(platform);
 
-        /* Retry, if we failed due to a cache resync. That can happen when the netlink
-         * socket fills up and we lost the response. */
-    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC && ++try_count < 10);
+        nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
+
+    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+             && ++try_count < RESYNC_RETRIES);
 
     if (seq_result < 0) {
         /* negative seq_result is an errno from kernel. Map it to negative
@@ -9841,29 +9982,37 @@ ip_route_get(NMPlatform   *platform,
 static int
 routing_rule_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformRoutingRule *routing_rule)
 {
-    WaitForNlResponseResult      seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+    WaitForNlResponseResult      seq_result;
     nm_auto_nlmsg struct nl_msg *msg        = NULL;
-    gs_free char                *errmsg     = NULL;
+    gs_free char                *extack_msg = NULL;
     char                         s_buf[256];
     int                          nle;
+    int                          try_count = 0;
 
     msg = _nl_msg_new_routing_rule(RTM_NEWRULE, flags, routing_rule);
 
     event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &errmsg);
-    if (nle < 0) {
-        _LOGE("do-add-rule: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
-        return -NME_PL_NETLINK;
-    }
+    do {
+        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+        nle        = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &extack_msg);
+        if (nle < 0) {
+            _LOGE("do-add-rule: failed sending netlink request \"%s\" (%d)",
+                  nm_strerror(nle),
+                  -nle);
+            return -NME_PL_NETLINK;
+        }
 
-    delayed_action_handle_all(platform);
+        delayed_action_handle_all(platform);
+
+        nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
 
-    nm_assert(seq_result);
+    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+             && ++try_count < RESYNC_RETRIES);
 
     _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
            "do-add-rule: %s",
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
+           wait_for_nl_response_to_string(seq_result, extack_msg, s_buf, sizeof(s_buf)));
 
     if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
         return 0;
@@ -9877,11 +10026,12 @@ routing_rule_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformRoutin
 static int
 qdisc_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformQdisc *qdisc)
 {
-    WaitForNlResponseResult      seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char                *errmsg     = NULL;
+    WaitForNlResponseResult      seq_result;
+    gs_free char                *extack_msg = NULL;
     int                          nle;
     char                         s_buf[256];
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
+    nm_auto_nlmsg struct nl_msg *msg       = NULL;
+    int                          try_count = 0;
 
     /* Note: @qdisc must not be copied or kept alive because the lifetime of qdisc.kind
      * is undefined. */
@@ -9890,19 +10040,26 @@ qdisc_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformQdisc *qdisc)
 
     event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &errmsg);
-    if (nle < 0) {
-        _LOGE("do-add-qdisc: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
-        return -NME_PL_NETLINK;
-    }
+    do {
+        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+        nle        = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &extack_msg);
+        if (nle < 0) {
+            _LOGE("do-add-qdisc: failed sending netlink request \"%s\" (%d)",
+                  nm_strerror(nle),
+                  -nle);
+            return -NME_PL_NETLINK;
+        }
 
-    delayed_action_handle_all(platform);
+        delayed_action_handle_all(platform);
 
-    nm_assert(seq_result);
+        nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
+
+    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+             && ++try_count < RESYNC_RETRIES);
 
     _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
            "do-add-qdisc: %s",
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
+           wait_for_nl_response_to_string(seq_result, extack_msg, s_buf, sizeof(s_buf)));
 
     if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
         return 0;
@@ -9918,8 +10075,8 @@ tc_delete(NMPlatform *platform,
           guint32     parent,
           gboolean    log_error)
 {
-    WaitForNlResponseResult      seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char                *errmsg     = NULL;
+    WaitForNlResponseResult      seq_result;
+    gs_free char                *extack_msg = NULL;
     int                          nle;
     char                         s_buf[256];
     const char                  *log_tag;
@@ -9928,6 +10085,7 @@ tc_delete(NMPlatform *platform,
                   .tcm_ifindex = ifindex,
                   .tcm_parent  = parent,
     };
+    int try_count = 0;
 
     switch (nlmsg_type) {
     case RTM_DELQDISC:
@@ -9948,25 +10106,30 @@ tc_delete(NMPlatform *platform,
 
     event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &errmsg);
-    if (nle < 0) {
-        _NMLOG(log_error ? LOGL_ERR : LOGL_DEBUG,
-               "%s: failed sending netlink request \"%s\" (%d)",
-               log_tag,
-               nm_strerror(nle),
-               -nle);
-        return -NME_PL_NETLINK;
-    }
+    do {
+        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+        nle        = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &extack_msg);
+        if (nle < 0) {
+            _NMLOG(log_error ? LOGL_ERR : LOGL_DEBUG,
+                   "%s: failed sending netlink request \"%s\" (%d)",
+                   log_tag,
+                   nm_strerror(nle),
+                   -nle);
+            return -NME_PL_NETLINK;
+        }
 
-    delayed_action_handle_all(platform);
+        delayed_action_handle_all(platform);
+
+        nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
 
-    nm_assert(seq_result);
+    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+             && ++try_count < RESYNC_RETRIES);
 
     _NMLOG((seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK || !log_error) ? LOGL_DEBUG
                                                                                  : LOGL_WARN,
            "%s: %s",
            log_tag,
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
+           wait_for_nl_response_to_string(seq_result, extack_msg, s_buf, sizeof(s_buf)));
 
     if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
         return 0;
@@ -9987,11 +10150,12 @@ qdisc_delete(NMPlatform *platform, int ifindex, guint32 parent, gboolean log_err
 static int
 tfilter_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformTfilter *tfilter)
 {
-    WaitForNlResponseResult      seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
-    gs_free char                *errmsg     = NULL;
+    WaitForNlResponseResult      seq_result;
+    gs_free char                *extack_msg = NULL;
     int                          nle;
     char                         s_buf[256];
-    nm_auto_nlmsg struct nl_msg *msg = NULL;
+    nm_auto_nlmsg struct nl_msg *msg       = NULL;
+    int                          try_count = 0;
 
     /* Note: @tfilter must not be copied or kept alive because the lifetime of tfilter.kind
      * and tfilter.action.kind is undefined. */
@@ -10000,19 +10164,26 @@ tfilter_add(NMPlatform *platform, NMPNlmFlags flags, const NMPlatformTfilter *tf
 
     event_handler_read_netlink(platform, NMP_NETLINK_ROUTE, FALSE);
 
-    nle = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &errmsg);
-    if (nle < 0) {
-        _LOGE("do-add-tfilter: failed sending netlink request \"%s\" (%d)", nm_strerror(nle), -nle);
-        return -NME_PL_NETLINK;
-    }
+    do {
+        seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
+        nle        = _netlink_send_nlmsg_rtnl(platform, msg, &seq_result, &extack_msg);
+        if (nle < 0) {
+            _LOGE("do-add-tfilter: failed sending netlink request \"%s\" (%d)",
+                  nm_strerror(nle),
+                  -nle);
+            return -NME_PL_NETLINK;
+        }
 
-    delayed_action_handle_all(platform);
+        delayed_action_handle_all(platform);
 
-    nm_assert(seq_result);
+        nm_assert(seq_result != WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN);
 
-    _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
-           "do-add-tfilter: %s",
-           wait_for_nl_response_to_string(seq_result, errmsg, s_buf, sizeof(s_buf)));
+        _NMLOG(seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK ? LOGL_DEBUG : LOGL_WARN,
+               "do-add-tfilter: %s",
+               wait_for_nl_response_to_string(seq_result, extack_msg, s_buf, sizeof(s_buf)));
+
+    } while (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC
+             && ++try_count < RESYNC_RETRIES);
 
     if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK)
         return 0;
@@ -10268,9 +10439,16 @@ continue_reading:
             int errsv;
 
             errsv = nlmsg_parse_error(msg.nm_nlh, &extack_msg);
-            if (errsv == 0)
+            if (errsv == 0) {
                 seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
-            else {
+                if (extack_msg) {
+                    _LOGD("%s: recvmsg: warning message from kernel: %s%s%s for request %d",
+                          log_prefix,
+                          NM_PRINT_FMT_QUOTE_STRING(extack_msg),
+                          msg.nm_nlh->nlmsg_seq);
+                    extack_msg = NULL;
+                }
+            } else {
                 _LOGD("%s: recvmsg: error message from kernel: %s (%d)%s%s%s for request %d",
                       log_prefix,
                       nm_strerror(errsv),
@@ -10728,9 +10906,10 @@ mptcp_addr_update(NMPlatform *platform, NMOptionBool add, const NMPlatformMptcpA
         return nle;
     }
 
-    _LOGT("mptcp: %s address %s: success",
+    _LOGT("mptcp: %s address %s: success%s%s%s",
           cmd_str,
-          nm_platform_mptcp_addr_to_string(addr, sbuf, sizeof(sbuf)));
+          nm_platform_mptcp_addr_to_string(addr, sbuf, sizeof(sbuf)),
+          NM_PRINT_FMT_QUOTED(extack_msg[0] != '\0', " Warning: \"", extack_msg, "\"", ""));
 
     return 0;
 
@@ -11235,6 +11414,7 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass)
     platform_class->link_set_sriov_params_async        = link_set_sriov_params_async;
     platform_class->link_set_sriov_vfs                 = link_set_sriov_vfs;
     platform_class->link_set_bridge_vlans              = link_set_bridge_vlans;
+    platform_class->link_set_bridge_info               = link_set_bridge_info;
 
     platform_class->link_get_physical_port_id = link_get_physical_port_id;
     platform_class->link_get_dev_id           = link_get_dev_id;
diff --git a/src/libnm-platform/nm-netlink.c b/src/libnm-platform/nm-netlink.c
index fc704226..5684b8cd 100644
--- a/src/libnm-platform/nm-netlink.c
+++ b/src/libnm-platform/nm-netlink.c
@@ -313,9 +313,6 @@ nlmsg_parse_error(const struct nlmsghdr *nlh, const char **out_extack_msg)
 
     e = nlmsg_data(nlh);
 
-    if (!e->error)
-        return 0;
-
     if (NM_FLAGS_HAS(nlh->nlmsg_flags, NLM_F_ACK_TLVS) && out_extack_msg
         && nlh->nlmsg_len >= sizeof(*e) + e->msg.nlmsg_len) {
         static const struct nla_policy policy[] = {
@@ -328,11 +325,19 @@ nlmsg_parse_error(const struct nlmsghdr *nlh, const char **out_extack_msg)
         tlvs = NM_CAST_ALIGN(struct nlattr,
                              (((char *) e) + sizeof(*e) + e->msg.nlmsg_len - NLMSG_HDRLEN));
         if (nla_parse_arr(tb, tlvs, nlh->nlmsg_len - sizeof(*e) - e->msg.nlmsg_len, policy) >= 0) {
-            if (tb[NLMSGERR_ATTR_MSG])
-                *out_extack_msg = nla_get_string(tb[NLMSGERR_ATTR_MSG]);
+            if (tb[NLMSGERR_ATTR_MSG]) {
+                const char *s;
+
+                s = nla_get_string(tb[NLMSGERR_ATTR_MSG]);
+                if (s[0] != '\0')
+                    *out_extack_msg = s;
+            }
         }
     }
 
+    if (!e->error)
+        return 0;
+
     return -nm_errno_from_native(e->error);
 }
 
@@ -488,44 +493,52 @@ nlmsg_put(struct nl_msg *n,
 }
 
 size_t
-nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize)
+_nla_strlcpy_full(char *dst, const struct nlattr *nla, size_t dstsize, gboolean wipe_remainder)
 {
-    const char *src;
+    const char *src = NULL;
     size_t      srclen;
-    size_t      len;
-
-    /* - Always writes @dstsize bytes to @dst
-     * - Copies the first non-NUL characters to @dst.
-     *   Any characters after the first NUL bytes in @nla are ignored.
-     * - If the string @nla is longer than @dstsize, the string
-     *   gets truncated. @dst will always be NUL terminated. */
-
-    if (G_UNLIKELY(dstsize <= 1)) {
-        if (dstsize == 1)
-            dst[0] = '\0';
-        if (nla && (srclen = nla_len(nla)) > 0)
-            return strnlen(nla_data(nla), srclen);
-        return 0;
-    }
+    size_t      cpylen;
+
+    /* Behaves like strlcpy():
+     *
+     * - returns the length of the string in nla (how much it wanted to copy).
+     * - will always NUL terminate dst (unless dstsize is zero).
+     * - if @wipe_remainder, the remaining bytes after the string are set to NUL,
+     *   similar to what strncpy() would do. Otherwise the bytes are undefined.
+     * - nla is not required to contain a NUL terminated string (unlike nla_get_string()).
+     * - the function copies the bytes up to the first NUL character in nla.
+     *   any remainder in nla is ignored.
+     * - nla may be NULL, which is treated the same as an empty string (copying zero bytes).
+     */
 
-    nm_assert(dst);
+    nm_assert(dstsize == 0 || dst);
 
     if (nla) {
         srclen = nla_len(nla);
         if (srclen > 0) {
             src    = nla_data(nla);
             srclen = strnlen(src, srclen);
-            if (srclen > 0) {
-                len = NM_MIN(dstsize - 1, srclen);
-                memcpy(dst, src, len);
-                memset(&dst[len], 0, dstsize - len);
-                return srclen;
-            }
         }
+    } else
+        srclen = 0;
+
+    if (dstsize == 0) {
+        /* we cannot NUL terminate. This is potentially dangerous, maybe
+         * we should assert against this case. */
+        return srclen;
     }
 
-    memset(dst, 0, dstsize);
-    return 0;
+    cpylen = NM_MIN(dstsize - 1u, srclen);
+
+    nm_memcpy(dst, src, cpylen);
+
+    if (wipe_remainder) {
+        /* like strncpy() would do, wipe the rest. */
+        memset(&dst[cpylen], 0, dstsize - cpylen);
+    } else
+        dst[cpylen] = '\0';
+
+    return srclen;
 }
 
 size_t
@@ -1024,17 +1037,6 @@ nlmsg_get_dst(struct nl_msg *msg)
 }
 
 int
-nl_socket_set_nonblocking(const struct nl_sock *sk)
-{
-    nm_assert_sk(sk);
-
-    if (fcntl(sk->s_fd, F_SETFL, O_NONBLOCK) < 0)
-        return -nm_errno_from_native(errno);
-
-    return 0;
-}
-
-int
 nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf)
 {
     int err;
@@ -1483,13 +1485,13 @@ nl_send_auto(struct nl_sock *sk, struct nl_msg *msg)
  * @nla: (out): the source address on success.
  * @buf: (out): pointer to the result buffer on success. This is
  *   either @buf0 or an allocated buffer that gets returned.
- * @out_creds: (out) (allow-none): optional out buffer for the credentials
+ * @out_creds: (out) (optional): optional out buffer for the credentials
  *   on success.
- * @out_creds_has: (out) (allow-none): result indicating whether
+ * @out_creds_has: (out) (optional): result indicating whether
  *   @out_creds was filled.
-* @out_pktinfo_group: (out) (allow-none): optional out buffer for NETLINK_PKTINFO
+* @out_pktinfo_group: (out) (optional): optional out buffer for NETLINK_PKTINFO
 *    group on success.
- * @out_pktinfo_has: (out) (allow-none): result indicating whether
+ * @out_pktinfo_has: (out) (optional): result indicating whether
  *   @out_pktinfo_group was filled.
  *
  * If @buf0_len is zero, the function will g_malloc() a new receive buffer of size
diff --git a/src/libnm-platform/nm-netlink.h b/src/libnm-platform/nm-netlink.h
index cdb8a81c..efd482ad 100644
--- a/src/libnm-platform/nm-netlink.h
+++ b/src/libnm-platform/nm-netlink.h
@@ -93,19 +93,19 @@ 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)
@@ -238,10 +238,36 @@ nla_get_be64(const struct nlattr *nla)
 static inline char *
 nla_get_string(const struct nlattr *nla)
 {
-    return nla_data(nla);
+    char *s;
+
+    /* nla_get_string() requires that nla contains a NUL terminated string.
+     * It cannot return NULL. Only use it with attributes that validate as NLA_STRING. */
+
+    nm_assert(nla_len(nla) > 0);
+
+    s = nla_data(nla);
+
+    nm_assert(memchr(s, 0, nla_len(nla)));
+
+    return s;
 }
 
-size_t nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize);
+size_t
+_nla_strlcpy_full(char *dst, const struct nlattr *nla, size_t dstsize, gboolean wipe_remainder);
+
+static inline size_t
+nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize)
+{
+    return _nla_strlcpy_full(dst, nla, dstsize, FALSE);
+}
+
+static inline size_t
+nla_strlcpy_wipe(char *dst, const struct nlattr *nla, size_t dstsize)
+{
+    /* Behaves exactly like nla_strlcpy(), but (similar to strncpy()) it fills the
+     * remaining @dstsize bytes with NUL. */
+    return _nla_strlcpy_full(dst, nla, dstsize, TRUE);
+}
 
 size_t nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize);
 
@@ -264,6 +290,17 @@ size_t nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize);
     }                                                                    \
     G_STMT_END
 
+static inline struct in6_addr
+nla_get_in6_addr(const struct nlattr *nla)
+{
+    struct in6_addr in6;
+
+    nm_assert(nla_len(nla) >= sizeof(struct in6_addr));
+
+    nla_memcpy(&in6, nla, sizeof(in6));
+    return in6;
+}
+
 int nla_put(struct nl_msg *msg, int attrtype, int datalen, const void *data);
 
 static inline int
@@ -568,8 +605,6 @@ int nl_socket_set_passcred(struct nl_sock *sk, int state);
 
 int nl_socket_set_pktinfo(struct nl_sock *sk, int state);
 
-int nl_socket_set_nonblocking(const struct nl_sock *sk);
-
 uint32_t nl_socket_get_local_port(const struct nl_sock *sk);
 
 int nl_socket_add_memberships(struct nl_sock *sk, int group, ...);
diff --git a/src/libnm-platform/nm-platform-utils.c b/src/libnm-platform/nm-platform-utils.c
index bebc53a8..08d82fe8 100644
--- a/src/libnm-platform/nm-platform-utils.c
+++ b/src/libnm-platform/nm-platform-utils.c
@@ -1955,10 +1955,10 @@ nmp_utils_ip_config_source_to_string(NMIPConfigSource source, char *buf, gsize l
 /**
  * nmp_utils_sysctl_open_netdir:
  * @ifindex: the ifindex for which to open "/sys/class/net/%s"
- * @ifname_guess: (allow-none): optional argument, if present used as initial
+ * @ifname_guess: (nullable): optional argument, if present used as initial
  *   guess as the current name for @ifindex. If guessed right,
  *   it saves an additional if_indextoname() call.
- * @out_ifname: (allow-none): if present, must be at least IFNAMSIZ
+ * @out_ifname: (optional): if present, must be at least IFNAMSIZ
  *   characters. On success, this will contain the actual ifname
  *   found while opening the directory.
  *
@@ -2064,35 +2064,6 @@ nmp_utils_new_vlan_name(const char *parent_iface, guint32 vlan_id)
 
 /*****************************************************************************/
 
-/* nmp_utils_new_infiniband_name:
- * @name: the output-buffer where the value will be written. Must be
- *   not %NULL and point to a string buffer of at least IFNAMSIZ bytes.
- * @parent_name: the parent interface name
- * @p_key: the partition key.
- *
- * Returns: the infiniband name will be written to @name and @name
- *   is returned.
- */
-const char *
-nmp_utils_new_infiniband_name(char *name, const char *parent_name, int p_key)
-{
-    g_return_val_if_fail(name, NULL);
-    g_return_val_if_fail(parent_name && parent_name[0], NULL);
-    g_return_val_if_fail(strlen(parent_name) < IFNAMSIZ, NULL);
-
-    /* technically, p_key of 0x0000 and 0x8000 is not allowed either. But we don't
-     * want to assert against that in nmp_utils_new_infiniband_name(). So be more
-     * resilient here, and accept those. */
-    g_return_val_if_fail(p_key >= 0 && p_key <= 0xffff, NULL);
-
-    /* If parent+suffix is too long, kernel would just truncate
-     * the name. We do the same. See ipoib_vlan_add().  */
-    g_snprintf(name, IFNAMSIZ, "%s.%04x", parent_name, p_key);
-    return name;
-}
-
-/*****************************************************************************/
-
 /**
  * Takes a pair @timestamp and @duration, and returns the remaining duration based
  * on the new timestamp @now.
@@ -2209,7 +2180,7 @@ nmp_utils_modprobe(GError **error, gboolean suppress_error_logging, const char *
 
     /* construct the argument list */
     argv = g_ptr_array_sized_new(4);
-    g_ptr_array_add(argv, "/sbin/modprobe");
+    g_ptr_array_add(argv, MODPROBE_PATH);
     g_ptr_array_add(argv, "--use-blacklist");
     g_ptr_array_add(argv, (char *) arg1);
 
diff --git a/src/libnm-platform/nm-platform-utils.h b/src/libnm-platform/nm-platform-utils.h
index 9f17da48..14b09a79 100644
--- a/src/libnm-platform/nm-platform-utils.h
+++ b/src/libnm-platform/nm-platform-utils.h
@@ -77,8 +77,7 @@ int         nmp_utils_if_nametoindex(const char *ifname);
 
 int nmp_utils_sysctl_open_netdir(int ifindex, const char *ifname_guess, char *out_ifname);
 
-char       *nmp_utils_new_vlan_name(const char *parent_iface, guint32 vlan_id);
-const char *nmp_utils_new_infiniband_name(char *name, const char *parent_name, int p_key);
+char *nmp_utils_new_vlan_name(const char *parent_iface, guint32 vlan_id);
 
 guint32
 nmp_utils_lifetime_rebase_relative_time_on_now(guint32 timestamp, guint32 duration, gint32 now);
diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c
index 1c495d60..041354cf 100644
--- a/src/libnm-platform/nm-platform.c
+++ b/src/libnm-platform/nm-platform.c
@@ -1276,7 +1276,7 @@ _link_add_check_existing(NMPlatform            *self,
  * @type: Interface type
  * @name: Interface name
  * @parent: the IFLA_LINK parameter or 0.
- * @address: (allow-none): set the mac address of the link
+ * @address: (nullable): set the mac address of the link
  * @address_len: the length of the @address
  * @extra_data: depending on @type, additional data.
  * @out_link: on success, the link object
@@ -2060,6 +2060,29 @@ nm_platform_link_set_bridge_vlans(NMPlatform                        *self,
     return klass->link_set_bridge_vlans(self, ifindex, on_master, vlans);
 }
 
+gboolean
+nm_platform_link_set_bridge_info(NMPlatform                            *self,
+                                 int                                    ifindex,
+                                 const NMPlatformLinkSetBridgeInfoData *bridge_info)
+{
+    _CHECK_SELF(self, klass, FALSE);
+
+    g_return_val_if_fail(ifindex > 0, FALSE);
+
+    if (_LOGD_ENABLED()) {
+        if (bridge_info->vlan_filtering_has) {
+            _LOG3D("link: setting bridge vlan-filtering %s",
+                   bridge_info->vlan_filtering_val ? "on" : "off");
+        }
+
+        if (bridge_info->vlan_default_pvid_has) {
+            _LOG3D("link: setting bridge vlan-default-pvid %d", bridge_info->vlan_default_pvid_val);
+        }
+    }
+
+    return klass->link_set_bridge_info(self, ifindex, bridge_info);
+}
+
 /**
  * nm_platform_link_change_flags_full:
  * @self: platform instance
@@ -2146,7 +2169,11 @@ nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *name)
 }
 
 gboolean
-nm_platform_link_change(NMPlatform *self, int ifindex, NMPlatformLinkBondPort *bond_port)
+nm_platform_link_change(NMPlatform               *self,
+                        int                       ifindex,
+                        NMPlatformLinkProps      *props,
+                        NMPlatformLinkBondPort   *bond_port,
+                        NMPlatformLinkChangeFlags flags)
 {
     char sbuf_prio[100];
 
@@ -2154,9 +2181,24 @@ nm_platform_link_change(NMPlatform *self, int ifindex, NMPlatformLinkBondPort *b
 
     g_return_val_if_fail(ifindex >= 0, FALSE);
 
+    nm_assert(!NM_FLAGS_ANY(flags,
+                            NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH
+                                | NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE
+                                | NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS
+                                | NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE)
+              || props);
+
     if (_LOGD_ENABLED()) {
         nm_auto_free_gstring GString *str = g_string_new("");
 
+        if (flags & NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH)
+            g_string_append_printf(str, "tx-queue-length %u ", props->tx_queue_length);
+        if (flags & NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE)
+            g_string_append_printf(str, "gso_max_size %u ", props->gso_max_size);
+        if (flags & NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS)
+            g_string_append_printf(str, "gso_max_segments %u ", props->gso_max_segments);
+        if (flags & NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE)
+            g_string_append_printf(str, "gro_max_size %u ", props->gro_max_size);
         if (bond_port) {
             nm_assert(bond_port->prio_has || bond_port->prio == 0);
             g_string_append_printf(str,
@@ -2178,8 +2220,10 @@ nm_platform_link_change(NMPlatform *self, int ifindex, NMPlatformLinkBondPort *b
 
     return klass->link_change(self,
                               ifindex,
+                              props,
                               bond_port ? NM_PORT_KIND_BOND : NM_PORT_KIND_NONE,
-                              (const NMPlatformLinkPortData *) bond_port);
+                              (const NMPlatformLinkPortData *) bond_port,
+                              flags);
 }
 
 /**
@@ -2362,7 +2406,7 @@ nm_platform_link_can_assume(NMPlatform *self, int ifindex)
  * @self: the platform instance
  * @ifindex: the link ifindex to lookup
  * @link_type: filter by link-type.
- * @out_link: (allow-none): returns the platform link instance
+ * @out_link: (out) (optional) (nullable): returns the platform link instance
  *
  * If the function returns %NULL, that could mean that no such ifindex
  * exists, of that the link has no lnk data. You can find that out
@@ -2373,7 +2417,7 @@ nm_platform_link_can_assume(NMPlatform *self, int ifindex)
  * object if it is present. If you set link-type, you can be sure
  * that only a link type of the matching type is returned (or %NULL).
  *
- * Returns: the internal link lnk object. The returned object
+ * Returns: (nullable): the internal link lnk object. The returned object
  * is owned by the platform cache and must not be modified. Note
  * however, that the object is guaranteed to be immutable, so
  * you can safely take a reference and keep it for yourself
@@ -2631,7 +2675,7 @@ nm_platform_link_wireguard_change(NMPlatform                               *self
  * @vnet_hdr: whether to set the IFF_VNET_HDR flag
  * @multi_queue: whether to set the IFF_MULTI_QUEUE flag
  * @out_link: on success, the link object
- * @out_fd: (allow-none): if give, return the file descriptor for the
+ * @out_fd: (out) (optional): if give, return the file descriptor for the
  *   created device. Note that when creating a non-persistent device,
  *   this argument is mandatory, otherwise it makes no sense
  *   to create such an interface.
@@ -2727,7 +2771,7 @@ link_set_option(NMPlatform *self,
     path =
         nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option);
     return nm_platform_sysctl_set(self,
-                                  NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path),
+                                  NMP_SYSCTL_PATHID_NETDIR_UNSAFE_A(dirfd, ifname_verified, path),
                                   value);
 }
 
@@ -2748,7 +2792,7 @@ link_get_option(NMPlatform *self, int ifindex, const char *category, const char
     path =
         nm_sprintf_buf_unsafe_a(strlen(category) + strlen(option) + 2, "%s/%s", category, option);
     return nm_platform_sysctl_get(self,
-                                  NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname_verified, path));
+                                  NMP_SYSCTL_PATHID_NETDIR_UNSAFE_A(dirfd, ifname_verified, path));
 }
 
 static const char *
@@ -2955,7 +2999,7 @@ _infiniband_add_add_or_delete(NMPlatform            *self,
     if (parent_link->type != NM_LINK_TYPE_INFINIBAND)
         return -NME_PL_WRONG_TYPE;
 
-    nmp_utils_new_infiniband_name(name, parent_link->name, p_key);
+    nm_net_devname_infiniband(name, parent_link->name, p_key);
 
     if (add) {
         r = _link_add_check_existing(self, name, NM_LINK_TYPE_INFINIBAND, out_link);
@@ -3029,7 +3073,7 @@ nm_platform_link_infiniband_get_properties(NMPlatform  *self,
         return FALSE;
 
     contents =
-        nm_platform_sysctl_get(self, NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "mode"));
+        nm_platform_sysctl_get(self, NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_verified, "mode"));
     if (!contents)
         return FALSE;
     if (strstr(contents, "datagram"))
@@ -3040,13 +3084,13 @@ nm_platform_link_infiniband_get_properties(NMPlatform  *self,
         mode = NULL;
     g_free(contents);
 
-    p_key =
-        nm_platform_sysctl_get_int_checked(self,
-                                           NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname_verified, "pkey"),
-                                           16,
-                                           0,
-                                           0xFFFF,
-                                           -1);
+    p_key = nm_platform_sysctl_get_int_checked(
+        self,
+        NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname_verified, "pkey"),
+        16,
+        0,
+        0xFFFF,
+        -1);
     if (p_key < 0)
         return FALSE;
 
@@ -3093,7 +3137,7 @@ nm_platform_link_veth_get_properties(NMPlatform *self, int ifindex, int *out_pee
  * nm_platform_link_tun_get_properties:
  * @self: the #NMPlatform instance
  * @ifindex: the ifindex to look up
- * @out_properties: (out) (allow-none): return the read properties
+ * @out_properties: (out) (optional): return the read properties
  *
  * Only recent versions of kernel export tun properties via netlink.
  * So, if that's the case, then we have the NMPlatformLnkTun instance
@@ -3148,31 +3192,33 @@ nm_platform_link_tun_get_properties(NMPlatform *self, int ifindex, NMPlatformLnk
         if (dirfd < 0)
             return FALSE;
 
-        owner = nm_platform_sysctl_get_int_checked(self,
-                                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "owner"),
-                                                   10,
-                                                   -1,
-                                                   G_MAXUINT32,
-                                                   -2);
+        owner =
+            nm_platform_sysctl_get_int_checked(self,
+                                               NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "owner"),
+                                               10,
+                                               -1,
+                                               G_MAXUINT32,
+                                               -2);
         if (owner == -2)
             return FALSE;
 
-        group = nm_platform_sysctl_get_int_checked(self,
-                                                   NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "group"),
-                                                   10,
-                                                   -1,
-                                                   G_MAXUINT32,
-                                                   -2);
+        group =
+            nm_platform_sysctl_get_int_checked(self,
+                                               NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "group"),
+                                               10,
+                                               -1,
+                                               G_MAXUINT32,
+                                               -2);
         if (group == -2)
             return FALSE;
 
-        flags =
-            nm_platform_sysctl_get_int_checked(self,
-                                               NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, "tun_flags"),
-                                               16,
-                                               0,
-                                               G_MAXINT64,
-                                               -1);
+        flags = nm_platform_sysctl_get_int_checked(
+            self,
+            NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "tun_flags"),
+            16,
+            0,
+            G_MAXINT64,
+            -1);
         if (flags == -1)
             return FALSE;
     }
@@ -3634,7 +3680,8 @@ nm_platform_ip4_address_add(NMPlatform *self,
                             guint32     lifetime,
                             guint32     preferred,
                             guint32     flags,
-                            const char *label)
+                            const char *label,
+                            char      **out_extack_msg)
 {
     _CHECK_SELF(self, klass, FALSE);
 
@@ -3644,6 +3691,7 @@ nm_platform_ip4_address_add(NMPlatform *self,
     g_return_val_if_fail(preferred <= lifetime, FALSE);
     g_return_val_if_fail(!label || strlen(label) < sizeof(((NMPlatformIP4Address *) NULL)->label),
                          FALSE);
+    nm_assert(!out_extack_msg || !*out_extack_msg);
 
     if (_LOGD_ENABLED()) {
         char                 sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
@@ -3676,7 +3724,8 @@ nm_platform_ip4_address_add(NMPlatform *self,
                                   lifetime,
                                   preferred,
                                   flags,
-                                  label);
+                                  label,
+                                  out_extack_msg);
 }
 
 gboolean
@@ -3687,7 +3736,8 @@ nm_platform_ip6_address_add(NMPlatform     *self,
                             struct in6_addr peer_address,
                             guint32         lifetime,
                             guint32         preferred,
-                            guint32         flags)
+                            guint32         flags,
+                            char          **out_extack_msg)
 {
     _CHECK_SELF(self, klass, FALSE);
 
@@ -3695,6 +3745,7 @@ nm_platform_ip6_address_add(NMPlatform     *self,
     g_return_val_if_fail(plen <= 128, FALSE);
     g_return_val_if_fail(lifetime > 0, FALSE);
     g_return_val_if_fail(preferred <= lifetime, FALSE);
+    nm_assert(!out_extack_msg || !*out_extack_msg);
 
     if (_LOGD_ENABLED()) {
         char                 sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
@@ -3715,8 +3766,15 @@ nm_platform_ip6_address_add(NMPlatform     *self,
 
     nm_platform_ip6_dadfailed_set(self, ifindex, &address, FALSE);
 
-    return klass
-        ->ip6_address_add(self, ifindex, address, plen, peer_address, lifetime, preferred, flags);
+    return klass->ip6_address_add(self,
+                                  ifindex,
+                                  address,
+                                  plen,
+                                  peer_address,
+                                  lifetime,
+                                  preferred,
+                                  flags,
+                                  out_extack_msg);
 }
 
 gboolean
@@ -4102,7 +4160,7 @@ ip6_address_scope_cmp_descending(gconstpointer p_a, gconstpointer p_b, gpointer
  * @known_addresses: List of addresses. The list will be modified and
  *   expired addresses will be cleared (by calling nmp_object_unref()
  *   on the array element).
- * @addresses_prune: (allow-none): the list of addresses to delete.
+ * @addresses_prune: (nullable): the list of addresses to delete.
  *   If platform has such an address configured, it will be deleted
  *   at the beginning of the sync. Note that the array will be modified
  *   by the function.
@@ -4539,7 +4597,8 @@ next_plat:;
                     NM_FLAGS_HAS(flags, NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE)
                         ? IFA_F_NOPREFIXROUTE
                         : 0,
-                    known_address->a4.label))
+                    known_address->a4.label,
+                    NULL))
                 success = FALSE;
         } else {
             if (!nm_platform_ip6_address_add(
@@ -4553,7 +4612,8 @@ next_plat:;
                     (NM_FLAGS_HAS(flags, NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE)
                          ? IFA_F_NOPREFIXROUTE
                          : 0)
-                        | known_address->a6.n_ifa_flags))
+                        | known_address->a6.n_ifa_flags,
+                    NULL))
                 success = FALSE;
         }
     }
@@ -4594,42 +4654,6 @@ nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex)
 
 /*****************************************************************************/
 
-static gboolean
-_err_inval_due_to_ipv6_tentative_pref_src(NMPlatform *self, const NMPObject *obj)
-{
-    const NMPlatformIP6Route   *r;
-    const NMPlatformIP6Address *a;
-
-    nm_assert(NM_IS_PLATFORM(self));
-    nm_assert(NMP_OBJECT_IS_VALID(obj));
-
-    /* trying to add an IPv6 route with pref-src fails, if the address is
-     * still tentative (rh#1452684). We need to hack around that.
-     *
-     * Detect it, by guessing whether that's the case. */
-
-    if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP6_ROUTE)
-        return FALSE;
-
-    r = NMP_OBJECT_CAST_IP6_ROUTE(obj);
-
-    /* we only allow this workaround for routes added manually by the user. */
-    if (r->rt_source != NM_IP_CONFIG_SOURCE_USER)
-        return FALSE;
-
-    if (IN6_IS_ADDR_UNSPECIFIED(&r->pref_src))
-        return FALSE;
-
-    a = nm_platform_ip6_address_get(self, r->ifindex, &r->pref_src);
-    if (!a)
-        return FALSE;
-    if (!NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TENTATIVE)
-        || NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_DADFAILED))
-        return FALSE;
-
-    return TRUE;
-}
-
 static guint
 _ipv6_temporary_addr_prefixes_keep_hash(gconstpointer ptr)
 {
@@ -4951,15 +4975,15 @@ nm_platform_ip_route_get_prune_list(NMPlatform            *self,
  * @self: the #NMPlatform instance.
  * @addr_family: AF_INET or AF_INET6.
  * @ifindex: the @ifindex for which the routes are to be added.
- * @routes: (allow-none): a list of routes to configure. Must contain
+ * @routes: (nullable): a list of routes to configure. Must contain
  *   NMPObject instances of routes, according to @addr_family.
- * @routes_prune: (allow-none): the list of routes to delete.
+ * @routes_prune: (nullable): the list of routes to delete.
  *   If platform has such a route configured, it will be deleted
  *   at the end of the operation. Note that if @routes contains
  *   the same route, then it will not be deleted. @routes overrules
  *   @routes_prune list.
- * @out_temporary_not_available: (allow-none) (out): routes that could
- *   currently not be synced. The caller shall keep them and try later again.
+ * @out_routes_failed: (out) (optional) (nullable): routes that could
+ *   not be synced/added.
  *
  * Returns: %TRUE on success.
  */
@@ -4969,7 +4993,7 @@ nm_platform_ip_route_sync(NMPlatform *self,
                           int         ifindex,
                           GPtrArray  *routes,
                           GPtrArray  *routes_prune,
-                          GPtrArray **out_temporary_not_available)
+                          GPtrArray **out_routes_failed)
 {
     const int                      IS_IPv4 = NM_IS_IPv4(addr_family);
     const NMPlatformVTableRoute   *vt;
@@ -4989,8 +5013,8 @@ nm_platform_ip_route_sync(NMPlatform *self,
 
     for (i_type = 0; routes && i_type < 2; i_type++) {
         for (i = 0; i < routes->len; i++) {
-            int      r, r2;
-            gboolean gateway_route_added = FALSE;
+            gs_free char *extack_msg = NULL;
+            int           r;
 
             conf_o = routes->pdata[i];
 
@@ -5046,143 +5070,59 @@ nm_platform_ip_route_sync(NMPlatform *self,
                 }
             }
 
-sync_route_add:
             r = nm_platform_ip_route_add(self,
                                          NMP_NLM_FLAG_APPEND
                                              | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-                                         conf_o);
-            if (r < 0) {
-                if (r == -EEXIST) {
-                    /* Don't fail for EEXIST. It's not clear that the existing route
-                     * is identical to the one that we were about to add. However,
-                     * above we should have deleted conflicting (non-identical) routes. */
-                    if (_LOGD_ENABLED()) {
-                        plat_entry =
-                            nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o);
-                        if (!plat_entry) {
-                            _LOG3D("route-sync: adding route %s failed with EEXIST, however we "
-                                   "cannot find such a route",
-                                   nmp_object_to_string(conf_o,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf1,
-                                                        sizeof(sbuf1)));
-                        } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o),
-                                                 NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj),
-                                                 NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
-                                   != 0) {
-                            _LOG3D("route-sync: adding route %s failed due to existing "
-                                   "(different!) route %s",
-                                   nmp_object_to_string(conf_o,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf1,
-                                                        sizeof(sbuf1)),
-                                   nmp_object_to_string(plat_entry->obj,
-                                                        NMP_OBJECT_TO_STRING_PUBLIC,
-                                                        sbuf2,
-                                                        sizeof(sbuf2)));
-                        }
-                    }
-                } else if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) {
-                    _LOG3D("route-sync: ignore failure to add IPv%c route: %s: %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                } else if (r == -EINVAL && out_temporary_not_available
-                           && _err_inval_due_to_ipv6_tentative_pref_src(self, conf_o)) {
-                    _LOG3D("route-sync: ignore failure to add IPv6 route with tentative IPv6 "
-                           "pref-src: %s: %s",
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                    if (!*out_temporary_not_available)
-                        *out_temporary_not_available =
-                            g_ptr_array_new_full(0, (GDestroyNotify) nmp_object_unref);
-                    g_ptr_array_add(*out_temporary_not_available,
-                                    (gpointer) nmp_object_ref(conf_o));
-                } else if (!gateway_route_added
-                           && ((r == -ENETUNREACH && vt->is_ip4
-                                && !!NMP_OBJECT_CAST_IP4_ROUTE(conf_o)->gateway)
-                               || (r == -EHOSTUNREACH && !vt->is_ip4
-                                   && !IN6_IS_ADDR_UNSPECIFIED(
-                                       &NMP_OBJECT_CAST_IP6_ROUTE(conf_o)->gateway)))) {
-                    NMPObject oo;
-
-                    if (vt->is_ip4) {
-                        const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE(conf_o);
-
-                        nmp_object_stackinit(
-                            &oo,
-                            NMP_OBJECT_TYPE_IP4_ROUTE,
-                            &((NMPlatformIP4Route){
-                                .ifindex       = rt->ifindex,
-                                .network       = rt->gateway,
-                                .plen          = 32,
-                                .metric        = nm_platform_ip4_route_get_effective_metric(rt),
-                                .rt_source     = rt->rt_source,
-                                .table_coerced = nm_platform_ip_route_get_effective_table(
-                                    NM_PLATFORM_IP_ROUTE_CAST(rt)),
-                            }));
-                    } else {
-                        const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE(conf_o);
-
-                        nmp_object_stackinit(
-                            &oo,
-                            NMP_OBJECT_TYPE_IP6_ROUTE,
-                            &((NMPlatformIP6Route){
-                                .ifindex       = rt->ifindex,
-                                .network       = rt->gateway,
-                                .plen          = 128,
-                                .metric        = nm_platform_ip6_route_get_effective_metric(rt),
-                                .rt_source     = rt->rt_source,
-                                .table_coerced = nm_platform_ip_route_get_effective_table(
-                                    NM_PLATFORM_IP_ROUTE_CAST(rt)),
-                            }));
-                    }
-
-                    _LOG3D("route-sync: failure to add IPv%c route: %s: %s; try adding direct "
-                           "route to gateway %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r),
-                           nmp_object_to_string(&oo,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf2,
-                                                sizeof(sbuf2)));
-
-                    r2 = nm_platform_ip_route_add(self,
-                                                  NMP_NLM_FLAG_APPEND
-                                                      | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE,
-                                                  &oo);
-
-                    if (r2 < 0) {
-                        _LOG3D("route-sync: failure to add gateway IPv%c route: %s: %s",
-                               vt->is_ip4 ? '4' : '6',
+                                         conf_o,
+                                         &extack_msg);
+            if (r == 0) {
+                /* success */
+            } else if (r == -EEXIST) {
+                /* Don't fail for EEXIST. It's not clear that the existing route
+                 * is identical to the one that we were about to add. However,
+                 * above we should have deleted conflicting (non-identical) routes. */
+                if (_LOGD_ENABLED()) {
+                    plat_entry =
+                        nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o);
+                    if (!plat_entry) {
+                        _LOG3D("route-sync: adding route %s failed with EEXIST, however we "
+                               "cannot find such a route",
+                               nmp_object_to_string(conf_o,
+                                                    NMP_OBJECT_TO_STRING_PUBLIC,
+                                                    sbuf1,
+                                                    sizeof(sbuf1)));
+                    } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o),
+                                             NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj),
+                                             NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY)
+                               != 0) {
+                        _LOG3D("route-sync: adding route %s failed due to existing "
+                               "(different!) route %s",
                                nmp_object_to_string(conf_o,
                                                     NMP_OBJECT_TO_STRING_PUBLIC,
                                                     sbuf1,
                                                     sizeof(sbuf1)),
-                               nm_strerror(r2));
+                               nmp_object_to_string(plat_entry->obj,
+                                                    NMP_OBJECT_TO_STRING_PUBLIC,
+                                                    sbuf2,
+                                                    sizeof(sbuf2)));
                     }
+                }
+            } else {
+                _LOG3D(
+                    "route-sync: failure to add IPv%c route: %s: %s%s%s%s",
+                    vt->is_ip4 ? '4' : '6',
+                    nmp_object_to_string(conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof(sbuf1)),
+                    nm_strerror(r),
+                    NM_PRINT_FMT_QUOTED(extack_msg, " (", extack_msg, ")", ""));
 
-                    gateway_route_added = TRUE;
-                    goto sync_route_add;
-                } else {
-                    _LOG3W("route-sync: failure to add IPv%c route: %s: %s",
-                           vt->is_ip4 ? '4' : '6',
-                           nmp_object_to_string(conf_o,
-                                                NMP_OBJECT_TO_STRING_PUBLIC,
-                                                sbuf1,
-                                                sizeof(sbuf1)),
-                           nm_strerror(r));
-                    success = FALSE;
+                success = FALSE;
+
+                if (out_routes_failed) {
+                    if (!*out_routes_failed) {
+                        *out_routes_failed =
+                            g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+                    }
+                    g_ptr_array_add(*out_routes_failed, (gpointer) nmp_object_ref(conf_o));
                 }
             }
         }
@@ -5325,12 +5265,12 @@ nm_platform_ip_route_normalize(int addr_family, NMPlatformIPRoute *route)
 }
 
 static int
-_ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack)
+_ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack, char **out_extack_msg)
 {
     char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE];
     int  ifindex;
 
-    _CHECK_SELF(self, klass, FALSE);
+    _CHECK_SELF(self, klass, -NME_BUG);
 
     /* The caller already ensures that this is a stack allocated copy, that
      * - stays alive for the duration of the call.
@@ -5341,6 +5281,7 @@ _ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack)
     nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_stack),
                         NMP_OBJECT_TYPE_IP4_ROUTE,
                         NMP_OBJECT_TYPE_IP6_ROUTE));
+    nm_assert(!out_extack_msg || !*out_extack_msg);
 
     nm_assert(NMP_OBJECT_GET_TYPE(obj_stack) != NMP_OBJECT_TYPE_IP4_ROUTE
               || obj_stack->ip4_route.n_nexthops <= 1u || obj_stack->_ip4_route.extra_nexthops);
@@ -5362,11 +5303,14 @@ _ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack)
      *   is stack allocated (and the potential "extra_nexthops" array is
      *   guaranteed to stay alive too).
      */
-    return klass->ip_route_add(self, flags, obj_stack);
+    return klass->ip_route_add(self, flags, obj_stack, out_extack_msg);
 }
 
 int
-nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *obj)
+nm_platform_ip_route_add(NMPlatform      *self,
+                         NMPNlmFlags      flags,
+                         const NMPObject *obj,
+                         char           **out_extack_msg)
 {
     nm_auto_nmpobj const NMPObject *obj_keep_alive = NULL;
     NMPObject                       obj_stack;
@@ -5384,7 +5328,7 @@ nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *o
         obj_stack._ip4_route.extra_nexthops = obj->_ip4_route.extra_nexthops;
     }
 
-    return _ip_route_add(self, flags, &obj_stack);
+    return _ip_route_add(self, flags, &obj_stack, out_extack_msg);
 }
 
 int
@@ -5416,7 +5360,7 @@ nm_platform_ip4_route_add(NMPlatform                   *self,
                               &extra_nexthops_free);
     }
 
-    return _ip_route_add(self, flags, &obj);
+    return _ip_route_add(self, flags, &obj, NULL);
 }
 
 int
@@ -5425,7 +5369,7 @@ nm_platform_ip6_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPlatformI
     NMPObject obj;
 
     nmp_object_stackinit(&obj, NMP_OBJECT_TYPE_IP6_ROUTE, (const NMPlatformObject *) route);
-    return _ip_route_add(self, flags, &obj);
+    return _ip_route_add(self, flags, &obj, NULL);
 }
 
 gboolean
@@ -5951,7 +5895,7 @@ nm_platform_vlan_qos_mapping_to_string(const char             *name,
 /**
  * nm_platform_link_to_string:
  * @route: pointer to NMPlatformLink address structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @buf: (nullable): an optional buffer. If %NULL, a static buffer is used.
  * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting an link struct into a string representation.
@@ -6039,6 +5983,10 @@ nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len)
         "%s%s"    /* inet6_token */
         "%s%s"    /* driver */
         "%s%s"    /* port_data */
+        " tx-queue-len %u"
+        " gso-max-size %u"
+        " gso-max-segs %u"
+        " gro-max-size %u"
         " rx:%" G_GUINT64_FORMAT ",%" G_GUINT64_FORMAT " tx:%" G_GUINT64_FORMAT
         ",%" G_GUINT64_FORMAT,
         link->ifindex,
@@ -6072,6 +6020,10 @@ nm_platform_link_to_string(const NMPlatformLink *link, char *buf, gsize len)
         link->driver ? " driver " : "",
         link->driver ?: "",
         NM_PRINT_FMT_QUOTED2(str_port_data[0] != '\0', " ", str_port_data, ""),
+        link->link_props.tx_queue_length,
+        link->link_props.gso_max_size,
+        link->link_props.gso_max_segments,
+        link->link_props.gro_max_size,
         link->rx_packets,
         link->rx_bytes,
         link->tx_packets,
@@ -6167,6 +6119,7 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
     char sbuf_miimon[30];
     char sbuf_updelay[30];
     char sbuf_downdelay[30];
+    char sbuf_lacp_active[30];
     char sbuf_peer_notif_delay[60];
     char sbuf_resend_igmp[30];
     char sbuf_lp_interval[30];
@@ -6201,7 +6154,9 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
         " xmit_hash_policy %u"
         " num_gray_arp %u"
         " all_ports_active %u"
+        " arp_missed_max %u"
         " lacp_rate %u"
+        "%s" /* lacp_active */
         " ad_select %u"
         " use_carrier %d"
         "%s" /* tlb_dynamic_lb */,
@@ -6251,7 +6206,14 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
         lnk->xmit_hash_policy,
         lnk->num_grat_arp,
         lnk->all_ports_active,
+        lnk->arp_missed_max,
         lnk->lacp_rate,
+        lnk->lacp_active_has || lnk->lacp_active != 0
+            ? nm_sprintf_buf(sbuf_lacp_active,
+                             " lacp_active%s %u",
+                             !lnk->lacp_active_has ? "?" : "",
+                             lnk->lacp_active)
+            : "",
         lnk->ad_select,
         (int) lnk->use_carrier,
         lnk->tlb_dynamic_lb_has ? nm_sprintf_buf(sbuf_tlb_dynamic_lb,
@@ -6269,6 +6231,15 @@ nm_platform_lnk_bond_to_string(const NMPlatformLnkBond *lnk, char *buf, gsize le
             nm_strbuf_append_str(&buf, &len, nm_inet4_ntop(lnk->arp_ip_target[i], target));
         }
     }
+    if (lnk->ns_ip6_targets_num > 0) {
+        nm_strbuf_append_str(&buf, &len, " ns_ip6_target");
+        for (i = 0; i < lnk->ns_ip6_targets_num; i++) {
+            char target[INET6_ADDRSTRLEN];
+
+            nm_strbuf_append_c(&buf, &len, ' ');
+            nm_strbuf_append_str(&buf, &len, nm_inet6_ntop(&lnk->ns_ip6_target[i], target));
+        }
+    }
     return buf;
 }
 
@@ -6920,11 +6891,11 @@ _rtm_flags_to_string_full(char *buf, gsize buf_size, unsigned rtm_flags)
 /**
  * nm_platform_ip4_route_to_string:
  * @route: pointer to NMPlatformIP4Route route structure
- * @extra_nexthops: (allow-none): the route might be a ECMP multihop route
+ * @extra_nexthops: (nullable): the route might be a ECMP multihop route
  *   (with n_nexthops > 1). In that case, provide the list of extra hops
  *   to print too. It is allowed for a multihop route to omit the extra hops
  *   by passing NULL.
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @buf: (nullable): an optional buffer. If %NULL, a static buffer is used.
  * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting a route struct into a string representation.
@@ -6998,7 +6969,6 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route     *route,
         "%s"         /* rto_min */
         "%s"         /* quickack */
         "%s"         /* mtu */
-        "%s"         /* r_force_commit */
         "",
         nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
                                               str_type),
@@ -7065,8 +7035,7 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route     *route,
                                                        " mtu %s%" G_GUINT32_FORMAT,
                                                        route->lock_mtu ? "lock " : "",
                                                        route->mtu)
-                                      : "",
-        route->r_force_commit ? " force-commit" : "");
+                                      : "");
 
     if ((n_nexthops == 1 && route->ifindex > 0) || n_nexthops == 0) {
         /* A plain single hop route. Nothing extra to remark. */
@@ -7116,7 +7085,7 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route     *route,
 /**
  * nm_platform_ip6_route_to_string:
  * @route: pointer to NMPlatformIP6Route route structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @buf: (nullable): an optional buffer. If %NULL, a static buffer is used.
  * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting a route struct into a string representation.
@@ -7186,7 +7155,6 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
         "%s"         /* quickack */
         "%s"         /* mtu */
         "%s"         /* pref */
-        "%s"         /* r_force_commit */
         "",
         nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced),
                                               str_type),
@@ -7253,8 +7221,7 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz
             str_pref,
             " pref %s",
             nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2)))
-                       : "",
-        route->r_force_commit ? " force-commit" : "");
+                       : "");
 
     return buf;
 }
@@ -7904,6 +7871,10 @@ nm_platform_link_hash_update(const NMPlatformLink *obj, NMHashState *h)
                         obj->arptype,
                         obj->inet6_addr_gen_mode_inv,
                         obj->inet6_token,
+                        obj->link_props.tx_queue_length,
+                        obj->link_props.gso_max_size,
+                        obj->link_props.gso_max_segments,
+                        obj->link_props.gro_max_size,
                         obj->port_kind,
                         obj->rx_packets,
                         obj->rx_bytes,
@@ -7966,6 +7937,10 @@ nm_platform_link_cmp(const NMPlatformLink *a, const NMPlatformLink *b)
     if (a->l_broadcast.len)
         NM_CMP_FIELD_MEMCMP_LEN(a, b, l_broadcast.data, a->l_broadcast.len);
     NM_CMP_FIELD_MEMCMP(a, b, inet6_token);
+    NM_CMP_FIELD(a, b, link_props.tx_queue_length);
+    NM_CMP_FIELD(a, b, link_props.gso_max_size);
+    NM_CMP_FIELD(a, b, link_props.gso_max_segments);
+    NM_CMP_FIELD(a, b, link_props.gro_max_size);
     NM_CMP_FIELD(a, b, port_kind);
     switch (a->port_kind) {
     case NM_PORT_KIND_NONE:
@@ -8032,15 +8007,19 @@ nm_platform_lnk_bond_hash_update(const NMPlatformLnkBond *obj, NMHashState *h)
                         obj->ad_actor_system,
                         obj->ad_select,
                         obj->all_ports_active,
+                        obj->arp_missed_max,
                         obj->arp_ip_targets_num,
                         obj->fail_over_mac,
                         obj->lacp_rate,
+                        obj->lacp_active,
+                        obj->ns_ip6_targets_num,
                         obj->num_grat_arp,
                         obj->mode,
                         obj->primary_reselect,
                         obj->xmit_hash_policy,
                         NM_HASH_COMBINE_BOOLS(guint16,
                                               obj->downdelay_has,
+                                              obj->lacp_active_has,
                                               obj->lp_interval_has,
                                               obj->miimon_has,
                                               obj->peer_notif_delay_has,
@@ -8051,6 +8030,7 @@ nm_platform_lnk_bond_hash_update(const NMPlatformLnkBond *obj, NMHashState *h)
                                               obj->use_carrier));
 
     nm_hash_update(h, obj->arp_ip_target, obj->arp_ip_targets_num * sizeof(obj->arp_ip_target[0]));
+    nm_hash_update(h, obj->ns_ip6_target, obj->ns_ip6_targets_num * sizeof(obj->ns_ip6_target[0]));
 }
 
 int
@@ -8069,6 +8049,11 @@ nm_platform_lnk_bond_cmp(const NMPlatformLnkBond *a, const NMPlatformLnkBond *b)
 {
     NM_CMP_SELF(a, b);
     NM_CMP_FIELD(a, b, arp_ip_targets_num);
+    NM_CMP_FIELD(a, b, ns_ip6_targets_num);
+    NM_CMP_FIELD_MEMCMP_LEN(a,
+                            b,
+                            ns_ip6_target,
+                            a->ns_ip6_targets_num * sizeof(a->ns_ip6_target[0]));
     NM_CMP_FIELD_MEMCMP_LEN(a,
                             b,
                             arp_ip_target,
@@ -8090,13 +8075,16 @@ nm_platform_lnk_bond_cmp(const NMPlatformLnkBond *a, const NMPlatformLnkBond *b)
     NM_CMP_FIELD_MEMCMP(a, b, ad_actor_system);
     NM_CMP_FIELD(a, b, ad_select);
     NM_CMP_FIELD(a, b, all_ports_active);
+    NM_CMP_FIELD(a, b, arp_missed_max);
     NM_CMP_FIELD(a, b, fail_over_mac);
     NM_CMP_FIELD(a, b, lacp_rate);
+    NM_CMP_FIELD(a, b, lacp_active);
     NM_CMP_FIELD(a, b, num_grat_arp);
     NM_CMP_FIELD(a, b, mode);
     NM_CMP_FIELD(a, b, primary_reselect);
     NM_CMP_FIELD(a, b, xmit_hash_policy);
     NM_CMP_FIELD_BOOL(a, b, downdelay_has);
+    NM_CMP_FIELD_BOOL(a, b, lacp_active_has);
     NM_CMP_FIELD_BOOL(a, b, lp_interval_has);
     NM_CMP_FIELD_BOOL(a, b, miimon_has);
     NM_CMP_FIELD_BOOL(a, b, peer_notif_delay_has);
@@ -8654,8 +8642,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj,
                                                   obj->lock_initcwnd,
                                                   obj->lock_initrwnd,
                                                   obj->lock_mtu,
-                                                  obj->lock_mss,
-                                                  obj->r_force_commit));
+                                                  obj->lock_mss));
         break;
     }
 }
@@ -8798,8 +8785,6 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a,
         NM_CMP_FIELD(a, b, initrwnd);
         NM_CMP_FIELD(a, b, mtu);
         NM_CMP_FIELD(a, b, rto_min);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
-            NM_CMP_FIELD_UNSAFE(a, b, r_force_commit);
         break;
     }
     return 0;
@@ -8899,8 +8884,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj,
                                                   obj->lock_initcwnd,
                                                   obj->lock_initrwnd,
                                                   obj->lock_mtu,
-                                                  obj->lock_mss,
-                                                  obj->r_force_commit),
+                                                  obj->lock_mss),
                             obj->window,
                             obj->cwnd,
                             obj->initcwnd,
@@ -8990,8 +8974,6 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a,
             NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref));
         else
             NM_CMP_FIELD(a, b, rt_pref);
-        if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL)
-            NM_CMP_FIELD_UNSAFE(a, b, r_force_commit);
         break;
     }
     return 0;
@@ -9313,7 +9295,6 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr,
                                               int                         ifindex,
                                               guint32                     route_table,
                                               guint32                     route_metric,
-                                              gboolean                    force_commit,
                                               NMPlatformIP4Route         *dst)
 {
     in_addr_t network_4;
@@ -9343,15 +9324,14 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr,
     }
 
     *dst = (NMPlatformIP4Route){
-        .ifindex        = ifindex,
-        .rt_source      = NM_IP_CONFIG_SOURCE_KERNEL,
-        .network        = network_4,
-        .plen           = addr->plen,
-        .pref_src       = addr->address,
-        .table_coerced  = nm_platform_route_table_coerce(route_table),
-        .metric         = route_metric,
-        .scope_inv      = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK),
-        .r_force_commit = force_commit,
+        .ifindex       = ifindex,
+        .rt_source     = NM_IP_CONFIG_SOURCE_KERNEL,
+        .network       = network_4,
+        .plen          = addr->plen,
+        .pref_src      = addr->address,
+        .table_coerced = nm_platform_route_table_coerce(route_table),
+        .metric        = route_metric,
+        .scope_inv     = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK),
     };
 
     nm_platform_ip_route_normalize(AF_INET, (NMPlatformIPRoute *) dst);
diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h
index d442ad9b..aeea5c42 100644
--- a/src/libnm-platform/nm-platform.h
+++ b/src/libnm-platform/nm-platform.h
@@ -150,6 +150,21 @@ GBytes       *nmp_link_address_get_as_bytes(const NMPLinkAddress *addr);
 
 #define NM_PLATFORM_LINK_OTHER_NETNS (-1)
 
+typedef struct {
+    guint32 tx_queue_length;
+    guint32 gso_max_size;
+    guint32 gso_max_segments;
+    guint32 gro_max_size;
+} NMPlatformLinkProps;
+
+typedef enum {
+    NM_PLATFORM_LINK_CHANGE_NONE             = 0,
+    NM_PLATFORM_LINK_CHANGE_TX_QUEUE_LENGTH  = (1 << 0),
+    NM_PLATFORM_LINK_CHANGE_GSO_MAX_SIZE     = (1 << 1),
+    NM_PLATFORM_LINK_CHANGE_GSO_MAX_SEGMENTS = (1 << 2),
+    NM_PLATFORM_LINK_CHANGE_GRO_MAX_SIZE     = (1 << 3),
+} NMPlatformLinkChangeFlags;
+
 struct _NMPlatformObjWithIfindex {
     __NMPlatformObjWithIfindex_COMMON;
 } _nm_alignas(NMPlatformObject);
@@ -166,7 +181,7 @@ typedef union {
 
 struct _NMPlatformLink {
     __NMPlatformObjWithIfindex_COMMON;
-    char       name[NMP_IFNAMSIZ];
+    char       name[NM_IFNAMSIZ];
     NMLinkType type;
 
     /* rtnl_link_get_type(), IFLA_INFO_KIND. */
@@ -214,6 +229,8 @@ struct _NMPlatformLink {
     guint64 tx_packets;
     guint64 tx_bytes;
 
+    NMPlatformLinkProps link_props;
+
     /* an interface can only hold IFLA_INFO_SLAVE_DATA for one link type */
     NMPlatformLinkPortData port_data;
 
@@ -242,13 +259,6 @@ typedef enum {
 
 guint _nm_platform_signal_id_get(NMPlatformSignalIdType signal_type);
 
-typedef enum {
-    NM_PLATFORM_SIGNAL_NONE,
-    NM_PLATFORM_SIGNAL_ADDED,
-    NM_PLATFORM_SIGNAL_CHANGED,
-    NM_PLATFORM_SIGNAL_REMOVED,
-} NMPlatformSignalChangeType;
-
 /* Default value for adding an IPv4 route. This is also what iproute2 does.
  * Note that contrary to IPv6, you can add routes with metric 0 and it is even
  * the default.
@@ -535,8 +545,8 @@ typedef struct {
     NMFibRuleUidRange  uid_range;                  /* FRA_UID_RANGE */
     NMFibRulePortRange sport_range;                /* FRA_SPORT_RANGE */
     NMFibRulePortRange dport_range;                /* FRA_DPORT_RANGE */
-    char               iifname[NMP_IFNAMSIZ];      /* FRA_IIFNAME */
-    char               oifname[NMP_IFNAMSIZ];      /* FRA_OIFNAME */
+    char               iifname[NM_IFNAMSIZ];       /* FRA_IIFNAME */
+    char               oifname[NM_IFNAMSIZ];       /* FRA_OIFNAME */
     guint8             addr_family;                /* (struct fib_rule_hdr).family */
     guint8             action;                     /* (struct fib_rule_hdr).action */
     guint8             tos;                        /* (struct fib_rule_hdr).tos */
@@ -725,6 +735,13 @@ typedef struct {
 } NMPlatformBridgeVlan;
 
 typedef struct {
+    guint16 vlan_default_pvid_val;
+    bool    vlan_filtering_val : 1;
+    bool    vlan_default_pvid_has : 1;
+    bool    vlan_filtering_has : 1;
+} NMPlatformLinkSetBridgeInfoData;
+
+typedef struct {
     guint64     mcast_last_member_interval;
     guint64     mcast_membership_interval;
     guint64     mcast_querier_interval;
@@ -756,40 +773,45 @@ extern const NMPlatformLnkBridge nm_platform_lnk_bridge_default;
 #define NM_BOND_MAX_ARP_TARGETS 16
 
 typedef struct {
-    int         primary;
-    in_addr_t   arp_ip_target[NM_BOND_MAX_ARP_TARGETS];
-    guint32     arp_all_targets;
-    guint32     arp_interval;
-    guint32     arp_validate;
-    guint32     downdelay;
-    guint32     lp_interval;
-    guint32     miimon;
-    guint32     min_links;
-    guint32     packets_per_port;
-    guint32     peer_notif_delay;
-    guint32     resend_igmp;
-    guint32     updelay;
-    guint16     ad_actor_sys_prio;
-    guint16     ad_user_port_key;
-    NMEtherAddr ad_actor_system;
-    guint8      ad_select;
-    guint8      all_ports_active;
-    guint8      arp_ip_targets_num;
-    guint8      fail_over_mac;
-    guint8      lacp_rate;
-    guint8      num_grat_arp;
-    guint8      mode;
-    guint8      primary_reselect;
-    guint8      xmit_hash_policy;
-    bool        downdelay_has : 1;
-    bool        lp_interval_has : 1;
-    bool        miimon_has : 1;
-    bool        peer_notif_delay_has : 1;
-    bool        resend_igmp_has : 1;
-    bool        tlb_dynamic_lb : 1;
-    bool        tlb_dynamic_lb_has : 1;
-    bool        updelay_has : 1;
-    bool        use_carrier : 1;
+    struct in6_addr ns_ip6_target[NM_BOND_MAX_ARP_TARGETS];
+    int             primary;
+    in_addr_t       arp_ip_target[NM_BOND_MAX_ARP_TARGETS];
+    guint32         arp_all_targets;
+    guint32         arp_interval;
+    guint32         arp_validate;
+    guint32         downdelay;
+    guint32         lp_interval;
+    guint32         miimon;
+    guint32         min_links;
+    guint32         packets_per_port;
+    guint32         peer_notif_delay;
+    guint32         resend_igmp;
+    guint32         updelay;
+    guint16         ad_actor_sys_prio;
+    guint16         ad_user_port_key;
+    NMEtherAddr     ad_actor_system;
+    guint8          ad_select;
+    guint8          all_ports_active;
+    guint8          arp_missed_max;
+    guint8          arp_ip_targets_num;
+    guint8          fail_over_mac;
+    guint8          lacp_active;
+    guint8          lacp_rate;
+    guint8          ns_ip6_targets_num;
+    guint8          num_grat_arp;
+    guint8          mode;
+    guint8          primary_reselect;
+    guint8          xmit_hash_policy;
+    bool            downdelay_has : 1;
+    bool            lacp_active_has : 1;
+    bool            lp_interval_has : 1;
+    bool            miimon_has : 1;
+    bool            peer_notif_delay_has : 1;
+    bool            resend_igmp_has : 1;
+    bool            tlb_dynamic_lb : 1;
+    bool            tlb_dynamic_lb_has : 1;
+    bool            updelay_has : 1;
+    bool            use_carrier : 1;
 } _nm_alignas(NMPlatformObject) NMPlatformLnkBond;
 
 typedef struct {
@@ -1110,8 +1132,10 @@ typedef struct {
                              gconstpointer extra_data);
     gboolean (*link_change)(NMPlatform                   *self,
                             int                           ifindex,
+                            NMPlatformLinkProps          *props,
                             NMPortKind                    port_kind,
-                            const NMPlatformLinkPortData *port_data);
+                            const NMPlatformLinkPortData *port_data,
+                            NMPlatformLinkChangeFlags     flags);
     gboolean (*link_delete)(NMPlatform *self, int ifindex);
     gboolean (*link_refresh)(NMPlatform *self, int ifindex);
     gboolean (*link_set_netns)(NMPlatform *self, int ifindex, int netns_fd);
@@ -1141,6 +1165,9 @@ typedef struct {
                                       int                                ifindex,
                                       gboolean                           on_master,
                                       const NMPlatformBridgeVlan *const *vlans);
+    gboolean (*link_set_bridge_info)(NMPlatform                            *self,
+                                     int                                    ifindex,
+                                     const NMPlatformLinkSetBridgeInfoData *bridge_info);
 
     char *(*link_get_physical_port_id)(NMPlatform *self, int ifindex);
     guint (*link_get_dev_id)(NMPlatform *self, int ifindex);
@@ -1241,7 +1268,8 @@ typedef struct {
                                 guint32     lifetime,
                                 guint32     preferred_lft,
                                 guint32     flags,
-                                const char *label);
+                                const char *label,
+                                char      **out_extack_msg);
     gboolean (*ip6_address_add)(NMPlatform     *self,
                                 int             ifindex,
                                 struct in6_addr address,
@@ -1249,7 +1277,8 @@ typedef struct {
                                 struct in6_addr peer_address,
                                 guint32         lifetime,
                                 guint32         preferred_lft,
-                                guint32         flags);
+                                guint32         flags,
+                                char          **out_extack_msg);
     gboolean (*ip4_address_delete)(NMPlatform *self,
                                    int         ifindex,
                                    in_addr_t   address,
@@ -1260,7 +1289,11 @@ typedef struct {
                                    struct in6_addr address,
                                    guint8          plen);
 
-    int (*ip_route_add)(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack);
+    int (*ip_route_add)(NMPlatform *self,
+                        NMPNlmFlags flags,
+                        NMPObject  *obj_stack,
+                        char      **out_extack_msg);
+
     int (*ip_route_get)(NMPlatform   *self,
                         int           addr_family,
                         gconstpointer address,
@@ -1501,23 +1534,30 @@ const char *nm_link_type_to_string(NMLinkType link_type);
 
 #define NMP_SYSCTL_PATHID_ABSOLUTE(path) ((const char *) NULL), -1, (path)
 
-#define NMP_SYSCTL_PATHID_NETDIR_unsafe(dirfd, ifname, path)                        \
-    nm_sprintf_buf_unsafe_a(NM_STRLEN("net:/sys/class/net//\0") + NMP_IFNAMSIZ + ({ \
-                                const gsize _l = strlen(path);                      \
-                                                                                    \
-                                nm_assert(_l < 200);                                \
-                                _l;                                                 \
-                            }),                                                     \
-                            "net:/sys/class/net/%s/%s",                             \
-                            (ifname),                                               \
-                            (path)),                                                \
+/* Uses alloca(). Use with care.
+ *
+ * Like NMP_SYSCTL_PATHID_NETDIR_A(), but "path" must not be a string literal.
+ * This is the "UNSAFE" part, where there is no compile time check for the
+ * maximum string length. It still must be reasonably short to not overflow
+ * the stack (the runtime assert checks for <200 chars). */
+#define NMP_SYSCTL_PATHID_NETDIR_UNSAFE_A(dirfd, ifname, path)                     \
+    nm_sprintf_buf_unsafe_a(NM_STRLEN("net:/sys/class/net//\0") + NM_IFNAMSIZ + ({ \
+                                const gsize _l = strlen(path);                     \
+                                                                                   \
+                                nm_assert(_l < 200);                               \
+                                _l;                                                \
+                            }),                                                    \
+                            "net:/sys/class/net/%s/%s",                            \
+                            (ifname),                                              \
+                            (path)),                                               \
         (dirfd), (path)
 
-#define NMP_SYSCTL_PATHID_NETDIR(dirfd, ifname, path)                            \
-    nm_sprintf_bufa(NM_STRLEN("net:/sys/class/net//" path "/\0") + NMP_IFNAMSIZ, \
-                    "net:/sys/class/net/%s/%s",                                  \
-                    (ifname),                                                    \
-                    path),                                                       \
+/* Uses alloca(). Use with care. */
+#define NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, path)                         \
+    nm_sprintf_bufa(NM_STRLEN("net:/sys/class/net//" path "/\0") + NM_IFNAMSIZ, \
+                    "net:/sys/class/net/%s/%s",                                 \
+                    (ifname),                                                   \
+                    path),                                                      \
         (dirfd), ("" path "")
 
 int      nm_platform_sysctl_open_netdir(NMPlatform *self, int ifindex, char *out_ifname);
@@ -1939,7 +1979,11 @@ nm_platform_link_change_flags(NMPlatform *self, int ifindex, unsigned value, gbo
     return nm_platform_link_change_flags_full(self, ifindex, value, set ? value : 0u);
 }
 
-gboolean nm_platform_link_change(NMPlatform *self, int ifindex, NMPlatformLinkBondPort *bond_port);
+gboolean nm_platform_link_change(NMPlatform               *self,
+                                 int                       ifindex,
+                                 NMPlatformLinkProps      *props,
+                                 NMPlatformLinkBondPort   *bond_port,
+                                 NMPlatformLinkChangeFlags flags);
 
 gboolean    nm_platform_link_get_udev_property(NMPlatform  *self,
                                                int          ifindex,
@@ -1978,6 +2022,9 @@ gboolean nm_platform_link_set_bridge_vlans(NMPlatform                        *se
                                            int                                ifindex,
                                            gboolean                           on_master,
                                            const NMPlatformBridgeVlan *const *vlans);
+gboolean nm_platform_link_set_bridge_info(NMPlatform                            *self,
+                                          int                                    ifindex,
+                                          const NMPlatformLinkSetBridgeInfoData *bridge_info);
 
 char    *nm_platform_link_get_physical_port_id(NMPlatform *self, int ifindex);
 guint    nm_platform_link_get_dev_id(NMPlatform *self, int ifindex);
@@ -2161,7 +2208,8 @@ gboolean nm_platform_ip4_address_add(NMPlatform *self,
                                      guint32     lifetime,
                                      guint32     preferred_lft,
                                      guint32     flags,
-                                     const char *label);
+                                     const char *label,
+                                     char      **out_extack_msg);
 gboolean nm_platform_ip6_address_add(NMPlatform     *self,
                                      int             ifindex,
                                      struct in6_addr address,
@@ -2169,7 +2217,8 @@ gboolean nm_platform_ip6_address_add(NMPlatform     *self,
                                      struct in6_addr peer_address,
                                      guint32         lifetime,
                                      guint32         preferred_lft,
-                                     guint32         flags);
+                                     guint32         flags,
+                                     char          **out_extack_msg);
 gboolean nm_platform_ip4_address_delete(NMPlatform *self,
                                         int         ifindex,
                                         in_addr_t   address,
@@ -2282,7 +2331,23 @@ nm_platform_ip_route_get_gateway(int addr_family, const NMPlatformIPRoute *route
     return &((NMPlatformIP6Route *) route)->gateway;
 }
 
-int nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *route);
+static inline gconstpointer
+nm_platform_ip_route_get_pref_src(int addr_family, const NMPlatformIPRoute *route)
+{
+    nm_assert_addr_family(addr_family);
+
+    if (!route)
+        return NULL;
+
+    if (NM_IS_IPv4(addr_family))
+        return &((NMPlatformIP4Route *) route)->pref_src;
+    return &((NMPlatformIP6Route *) route)->pref_src;
+}
+
+int nm_platform_ip_route_add(NMPlatform      *self,
+                             NMPNlmFlags      flags,
+                             const NMPObject *route,
+                             char           **out_extack_msg);
 int nm_platform_ip4_route_add(NMPlatform                   *self,
                               NMPNlmFlags                   flags,
                               const NMPlatformIP4Route     *route,
@@ -2299,7 +2364,7 @@ gboolean nm_platform_ip_route_sync(NMPlatform *self,
                                    int         ifindex,
                                    GPtrArray  *routes,
                                    GPtrArray  *routes_prune,
-                                   GPtrArray **out_temporary_not_available);
+                                   GPtrArray **out_routes_failed);
 
 gboolean nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex);
 
diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h
index 80d254b2..ffe59363 100644
--- a/src/libnm-platform/nmp-base.h
+++ b/src/libnm-platform/nmp-base.h
@@ -11,12 +11,6 @@
 
 /*****************************************************************************/
 
-/* IFNAMSIZ is both defined in <linux/if.h> and <net/if.h>. In the past, these
- * headers conflicted, so we cannot simply include either of them in a header-file.*/
-#define NMP_IFNAMSIZ 16
-
-/*****************************************************************************/
-
 /* Define of the IN6_ADDR_GEN_MODE_* values to workaround old kernel headers
  * that don't define it. */
 #define NM_IN6_ADDR_GEN_MODE_UNKNOWN        255 /* no corresponding value.  */
@@ -28,6 +22,15 @@
 /*****************************************************************************/
 
 typedef enum {
+    NM_PLATFORM_SIGNAL_NONE,
+    NM_PLATFORM_SIGNAL_ADDED,
+    NM_PLATFORM_SIGNAL_CHANGED,
+    NM_PLATFORM_SIGNAL_REMOVED,
+} NMPlatformSignalChangeType;
+
+/*****************************************************************************/
+
+typedef enum {
     NM_PLATFORM_LINK_DUPLEX_UNKNOWN,
     NM_PLATFORM_LINK_DUPLEX_HALF,
     NM_PLATFORM_LINK_DUPLEX_FULL,
diff --git a/src/libnm-platform/nmp-global-tracker.c b/src/libnm-platform/nmp-global-tracker.c
index 3fd31e4e..b06e9fe5 100644
--- a/src/libnm-platform/nmp-global-tracker.c
+++ b/src/libnm-platform/nmp-global-tracker.c
@@ -540,12 +540,13 @@ _track_data_untrack(NMPGlobalTracker *self,
              * sync. */
             obj_data->config_state = CONFIG_STATE_OWNED_BY_US;
         }
-    } else if (remove_user_tag_data && c_list_length_is(&track_data->user_tag_lst, 1))
+    } else if (remove_user_tag_data && c_list_is_empty_or_single(&track_data->user_tag_lst))
         g_hash_table_remove(self->by_user_tag, &track_data->user_tag);
 
     /* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry
      * around for the next sync -- so that we can undo what we did earlier. */
-    if (obj_data->config_state == CONFIG_STATE_NONE && c_list_length_is(&track_data->obj_lst, 1))
+    if (obj_data->config_state == CONFIG_STATE_NONE
+        && c_list_is_empty_or_single(&track_data->obj_lst))
         g_hash_table_remove(self->by_obj, &track_data->obj);
 
     g_hash_table_remove(self->by_data, track_data);
@@ -1101,7 +1102,7 @@ nmp_global_tracker_sync(NMPGlobalTracker *self, NMPObjectType obj_type, gboolean
                                          NMP_NLM_FLAG_ADD,
                                          NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj));
         } else
-            nm_platform_ip_route_add(self->platform, NMP_NLM_FLAG_APPEND, obj_data->obj);
+            nm_platform_ip_route_add(self->platform, NMP_NLM_FLAG_APPEND, obj_data->obj, NULL);
     }
 }
 
diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c
index 9ba027ba..7e3fb1a3 100644
--- a/src/libnm-platform/nmp-object.c
+++ b/src/libnm-platform/nmp-object.c
@@ -134,7 +134,7 @@ nm_sock_addr_union_hash_update(const NMSockAddrUnion *a, NMHashState *h)
  * @dst: the destination #NMSockAddrUnion. It will always be fully initialized,
  *   to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of
  *   error).
- * @src: (allow-none): the source buffer with an sockaddr to copy. It may be unaligned in
+ * @src: (nullable): the source buffer with an sockaddr to copy. It may be unaligned in
  *   memory. If not %NULL, the buffer must be at least large enough to contain
  *   sa.sa_family, and then, depending on sa.sa_family, it must be large enough
  *   to hold struct sockaddr_in or struct sockaddr_in6.
@@ -2047,10 +2047,10 @@ nmp_cache_link_connected_for_slave(int ifindex_master, const NMPObject *slave)
  * @cache: the platform cache
  * @master: the link object, that is checked whether its connected property
  *   needs to be toggled.
- * @potential_slave: (allow-none): an additional link object that is treated
+ * @potential_slave: (nullable): an additional link object that is treated
  *   as if it was inside @cache. If given, it shaddows a link in the cache
  *   with the same ifindex.
- * @ignore_slave: (allow-none): if set, the check will pretend that @ignore_slave
+ * @ignore_slave: (nullable): if set, the check will pretend that @ignore_slave
  *   is not in the cache.
  *
  * NMPlatformLink has two connected flags: (master->link.flags&IFF_LOWER_UP) (as reported
@@ -2114,10 +2114,10 @@ nmp_cache_link_connected_needs_toggle(const NMPCache  *cache,
  * @cache:
  * @master_ifindex: the ifindex of a potential master that should be checked
  *   whether it needs toggling.
- * @potential_slave: (allow-none): passed to nmp_cache_link_connected_needs_toggle().
+ * @potential_slave: (nullable): passed to nmp_cache_link_connected_needs_toggle().
  *   It considers @potential_slave as being inside the cache, replacing an existing
  *   link with the same ifindex.
- * @ignore_slave: (allow-onne): passed to nmp_cache_link_connected_needs_toggle().
+ * @ignore_slave: (nullable): passed to nmp_cache_link_connected_needs_toggle().
  *
  * The flag obj->link.connected depends on the state of other links in the
  * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex
@@ -2833,11 +2833,11 @@ nmp_cache_remove_netlink(NMPCache         *cache,
  *    afterwards. Hence, during a dump, every update should move the object to the
  *    end of the list, to obtain the correct order. That means, to use NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE,
  *    instead of NM_DEDUP_MULTI_IDX_MODE_APPEND.
- * @out_obj_old: (allow-none) (out): return the object with same ID as @obj_hand_over,
- *    that was in the cache before update. If an object is returned, the caller must
- *    unref it afterwards.
- * @out_obj_new: (allow-none) (out): return the object from the cache after update.
- *    The caller must unref this object.
+ * @out_obj_old: (out) (optional) (nullable) (transfer full): return the object
+ *    with same ID as @obj_hand_over, that was in the cache before update. If an
+ *    object is returned, the caller must unref it afterwards.
+ * @out_obj_new: (out) (optional) (nullable) (transfer full): return the object
+ *    from the cache after update. The caller must unref this object.
  *
  * Returns: how the cache changed.
  *
diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h
index 0d5f84b3..408f0318 100644
--- a/src/libnm-platform/nmp-object.h
+++ b/src/libnm-platform/nmp-object.h
@@ -1183,21 +1183,6 @@ nm_platform_lookup_object_by_addr_family(NMPlatform   *platform,
 
 /*****************************************************************************/
 
-static inline gboolean
-nmp_object_get_force_commit(const NMPObject *obj)
-{
-    switch (NMP_OBJECT_GET_TYPE(obj)) {
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-        return NMP_OBJECT_CAST_IP_ADDRESS(obj)->a_force_commit;
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-        return NMP_OBJECT_CAST_IP_ROUTE(obj)->r_force_commit;
-    default:
-        return nm_assert_unreachable_val(FALSE);
-    }
-}
-
 static inline const char *
 nmp_object_link_get_ifname(const NMPObject *obj)
 {
diff --git a/src/libnm-platform/nmp-plobj.c b/src/libnm-platform/nmp-plobj.c
index c27aa1fc..d3643ae1 100644
--- a/src/libnm-platform/nmp-plobj.c
+++ b/src/libnm-platform/nmp-plobj.c
@@ -373,7 +373,7 @@ nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr)
 /**
  * nm_platform_ip4_address_to_string:
  * @route: pointer to NMPlatformIP4Address address structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @buf: (nullable): an optional buffer. If %NULL, a static buffer is used.
  * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting an address struct into a string representation.
@@ -479,7 +479,7 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
 /**
  * nm_platform_ip6_address_to_string:
  * @route: pointer to NMPlatformIP6Address address structure
- * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
+ * @buf: (nullable): an optional buffer. If %NULL, a static buffer is used.
  * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
  *
  * A method for converting an address struct into a string representation.
diff --git a/src/libnm-platform/nmp-plobj.h b/src/libnm-platform/nmp-plobj.h
index e149150d..35c81509 100644
--- a/src/libnm-platform/nmp-plobj.h
+++ b/src/libnm-platform/nmp-plobj.h
@@ -113,7 +113,7 @@ struct _NMPlatformIP4Address {
      * See nm_platform_ip4_broadcast_address_from_addr(). */
     in_addr_t broadcast_address;
 
-    char label[NMP_IFNAMSIZ];
+    char label[NM_IFNAMSIZ];
 
     /* Whether the address is ready to be configured. By default, an address is, but this
      * flag may indicate that the address is just for tracking purpose only, but the ACD
@@ -255,7 +255,6 @@ NMPlatformIP4Route *nm_platform_ip4_address_generate_device_route(const NMPlatfo
                                                                   int                 ifindex,
                                                                   guint32             route_table,
                                                                   guint32             route_metric,
-                                                                  gboolean            force_commit,
                                                                   NMPlatformIP4Route *dst);
 
 typedef enum {
diff --git a/src/libnm-platform/tests/test-nm-platform.c b/src/libnm-platform/tests/test-nm-platform.c
index 90a29045..5fc8a5dd 100644
--- a/src/libnm-platform/tests/test-nm-platform.c
+++ b/src/libnm-platform/tests/test-nm-platform.c
@@ -117,7 +117,6 @@ test_use_symbols(void)
         (void (*)(void)) nl_socket_set_passcred,
         (void (*)(void)) nl_socket_set_msg_buf_size,
         (void (*)(void)) nlmsg_get_dst,
-        (void (*)(void)) nl_socket_set_nonblocking,
         (void (*)(void)) nl_socket_set_buffer_size,
         (void (*)(void)) nl_socket_add_memberships,
         (void (*)(void)) nl_wait_for_ack,
diff --git a/src/libnm-std-aux/c-list-util.h b/src/libnm-std-aux/c-list-util.h
index 4800a3cc..ae2f07ec 100644
--- a/src/libnm-std-aux/c-list-util.h
+++ b/src/libnm-std-aux/c-list-util.h
@@ -42,6 +42,12 @@ c_list_length_is(const CList *list, unsigned long check_len)
     return n == check_len;
 }
 
+static inline int
+c_list_is_empty_or_single(const CList *list)
+{
+    return !list || (list->next->next == list);
+}
+
 #define c_list_for_each_prev(_iter, _list) \
     for (_iter = (_list)->prev; (_iter) != (_list); _iter = (_iter)->prev)
 
diff --git a/src/libnm-std-aux/nm-std-aux.h b/src/libnm-std-aux/nm-std-aux.h
index a5e5abd3..d310305e 100644
--- a/src/libnm-std-aux/nm-std-aux.h
+++ b/src/libnm-std-aux/nm-std-aux.h
@@ -327,9 +327,9 @@ typedef uint64_t _nm_bitwise nm_be64_t;
      *
      * It's useful to check the let the compiler ensure that @value is
      * of a certain type. */
-#define _NM_ENSURE_TYPE(type, value) (_Generic((value), type : (value)))
+#define _NM_ENSURE_TYPE(type, value) (_Generic((value), type: (value)))
 #define _NM_ENSURE_TYPE_CONST(type, value) \
-    (_Generic((value), const type : ((const type)(value)), type : ((const type)(value))))
+    (_Generic((value), const type: ((const type)(value)), type: ((const type)(value))))
 #else
 #define _NM_ENSURE_TYPE(type, value)       (value)
 #define _NM_ENSURE_TYPE_CONST(type, value) ((const type)(value))
@@ -348,7 +348,7 @@ typedef uint64_t _nm_bitwise nm_be64_t;
 
 #if _NM_CC_SUPPORT_GENERIC && (!defined(__clang__) || __clang_major__ > 3)
 #define NM_STRUCT_OFFSET_ENSURE_TYPE(type, container, field) \
-    (_Generic((&(((container *) NULL)->field))[0], type : nm_offsetof(container, field)))
+    (_Generic((&(((container *) NULL)->field))[0], type: nm_offsetof(container, field)))
 #else
 #define NM_STRUCT_OFFSET_ENSURE_TYPE(type, container, field) nm_offsetof(container, field)
 #endif
@@ -1367,6 +1367,12 @@ nm_ptr_to_uintptr(const void *p)
 
 /*****************************************************************************/
 
+/* IFNAMSIZ is both defined in <linux/if.h> and <net/if.h>. In the past, these
+ * headers conflicted, so we cannot simply include either of them in a header-file.*/
+#define NM_IFNAMSIZ 16
+
+/*****************************************************************************/
+
 #define NM_AF_UNSPEC 0  /* AF_UNSPEC */
 #define NM_AF_INET   2  /* AF_INET   */
 #define NM_AF_INET6  10 /* AF_INET6  */
@@ -1419,6 +1425,9 @@ nm_utils_addr_family_to_char(int addr_family)
         (NM_UNIQ_T(_addr_family, uniq) == NM_AF_INET);           \
     })
 
+/* NM_IS_IPv4() is guaranteed to give either 0 or 1! That is an important
+ * guarantee, because we often use that value to index a 2-array (where at
+ * position zero is IPv6 and at position 1 IPv4). */
 #define NM_IS_IPv4(addr_family) _NM_IS_IPv4(NM_UNIQ, addr_family)
 
 static inline int
diff --git a/src/libnm-std-aux/nm-std-utils.c b/src/libnm-std-aux/nm-std-utils.c
index 18692b19..8901378c 100644
--- a/src/libnm-std-aux/nm-std-utils.c
+++ b/src/libnm-std-aux/nm-std-utils.c
@@ -7,9 +7,13 @@
 #include <stdint.h>
 #include <assert.h>
 #include <limits.h>
+#include <net/if.h>
 
 /*****************************************************************************/
 
+NM_STATIC_ASSERT(NM_IFNAMSIZ == IFNAMSIZ);
+
+/*****************************************************************************/
 size_t
 nm_utils_get_next_realloc_size(bool true_realloc, size_t requested)
 {
diff --git a/src/libnm-std-aux/unaligned-fundamental.h b/src/libnm-std-aux/unaligned-fundamental.h
new file mode 100644
index 00000000..a4c810a5
--- /dev/null
+++ b/src/libnm-std-aux/unaligned-fundamental.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdint.h>
+
+static inline uint16_t unaligned_read_ne16(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
+
+        return u->x;
+}
+
+static inline uint32_t unaligned_read_ne32(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
+
+        return u->x;
+}
+
+static inline uint64_t unaligned_read_ne64(const void *_u) {
+        const struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
+
+        return u->x;
+}
+
+static inline void unaligned_write_ne16(void *_u, uint16_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint16_t x; } *u = _u;
+
+        u->x = a;
+}
+
+static inline void unaligned_write_ne32(void *_u, uint32_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint32_t x; } *u = _u;
+
+        u->x = a;
+}
+
+static inline void unaligned_write_ne64(void *_u, uint64_t a) {
+        struct __attribute__((__packed__, __may_alias__)) { uint64_t x; } *u = _u;
+
+        u->x = a;
+}
diff --git a/src/libnm-std-aux/unaligned.h b/src/libnm-std-aux/unaligned.h
index 4100be08..04580cfb 100644
--- a/src/libnm-std-aux/unaligned.h
+++ b/src/libnm-std-aux/unaligned.h
@@ -4,6 +4,8 @@
 #include <endian.h>
 #include <stdint.h>
 
+#include "unaligned-fundamental.h"
+
 /* BE */
 
 static inline uint16_t unaligned_read_be16(const void *_u) {
@@ -79,21 +81,3 @@ static inline void unaligned_write_le64(void *_u, uint64_t a) {
 
         u->x = le64toh(a);
 }
-
-#if __BYTE_ORDER == __BIG_ENDIAN
-#define unaligned_read_ne16 unaligned_read_be16
-#define unaligned_read_ne32 unaligned_read_be32
-#define unaligned_read_ne64 unaligned_read_be64
-
-#define unaligned_write_ne16 unaligned_write_be16
-#define unaligned_write_ne32 unaligned_write_be32
-#define unaligned_write_ne64 unaligned_write_be64
-#else
-#define unaligned_read_ne16 unaligned_read_le16
-#define unaligned_read_ne32 unaligned_read_le32
-#define unaligned_read_ne64 unaligned_read_le64
-
-#define unaligned_write_ne16 unaligned_write_le16
-#define unaligned_write_ne32 unaligned_write_le32
-#define unaligned_write_ne64 unaligned_write_le64
-#endif
diff --git a/src/libnm-systemd-core/meson.build b/src/libnm-systemd-core/meson.build
index 753ca149..6175e42c 100644
--- a/src/libnm-systemd-core/meson.build
+++ b/src/libnm-systemd-core/meson.build
@@ -21,6 +21,7 @@ libnm_systemd_core = static_library(
     include_directories(
       'sd-adapt-core',
       'src/libsystemd-network',
+      'src/libsystemd/sd-device',
       'src/libsystemd/sd-event',
       'src/systemd',
     ),
diff --git a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c
index e4d2d2e1..c5ef63eb 100644
--- a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c
+++ b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.c
@@ -8,6 +8,7 @@
 #include "nm-sd-adapt-core.h"
 
 #include "fd-util.h"
+#include "sd-device.h"
 
 /*****************************************************************************/
 
@@ -19,3 +20,17 @@ asynchronous_close(int fd)
 }
 
 /*****************************************************************************/
+
+sd_device *
+sd_device_ref(sd_device *self)
+{
+    g_return_val_if_fail(!self, self);
+    return self;
+}
+
+sd_device *
+sd_device_unref(sd_device *self)
+{
+    g_return_val_if_fail(!self, self);
+    return self;
+}
diff --git a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
index 7ce1f8c9..9c317801 100644
--- a/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
+++ b/src/libnm-systemd-core/sd-adapt-core/nm-sd-adapt-core.h
@@ -40,6 +40,8 @@
 #include <sys/syscall.h>
 #include <sys/ioctl.h>
 
+#define HAVE_EPOLL_PWAIT2 0
+
 /* Missing in Linux 3.2.0, in Ubuntu 12.04 */
 #ifndef BPF_XOR
 #define BPF_XOR 0xa0
@@ -85,8 +87,14 @@ sd_notify(int unset_environment, const char *state)
 #include "sd-id128.h"
 #include "sparse-endian.h"
 #include "async.h"
-#include "util.h"
 
 #endif /* (NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_SYSTEMD */
 
+/*****************************************************************************/
+
+struct sd_device;
+
+struct sd_device *sd_device_ref(struct sd_device *self);
+struct sd_device *sd_device_unref(struct sd_device *self);
+
 #endif /* __NM_SD_ADAPT_CORE_H__ */
diff --git a/src/libnm-systemd-core/sd-adapt-core/device-util.h b/src/libnm-systemd-core/sd-adapt-core/sd-messages.h
index 637892c2..637892c2 100644
--- a/src/libnm-systemd-core/sd-adapt-core/device-util.h
+++ b/src/libnm-systemd-core/sd-adapt-core/sd-messages.h
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
index 2636d796..05d0585a 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.c
@@ -6,15 +6,11 @@
 #include <net/ethernet.h>
 #include <net/if_arp.h>
 
-#include "sd-device.h"
-#include "sd-id128.h"
-
 #include "dhcp-identifier.h"
 #include "netif-util.h"
 #include "siphash24.h"
 #include "sparse-endian.h"
 #include "string-table.h"
-#include "udev-util.h"
 
 #define HASH_KEY       SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)
 #define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03)
@@ -213,49 +209,21 @@ int dhcp_identifier_set_duid(
 #endif /* NM_IGNORED */
 
 int dhcp_identifier_set_iaid(
-                int ifindex,
+                sd_device *dev,
                 const struct hw_addr_data *hw_addr,
                 bool legacy_unstable_byteorder,
-                bool use_mac,
                 void *ret) {
 #if 0 /* NM_IGNORED */
 
-        /* name is a pointer to memory in the sd_device struct, so must
-         * have the same scope */
-        _cleanup_(sd_device_unrefp) sd_device *device = NULL;
         const char *name = NULL;
         uint32_t id32;
         uint64_t id;
-        int r;
 
-        assert(ifindex > 0);
         assert(hw_addr);
         assert(ret);
 
-        if (udev_available() && !use_mac) {
-                /* udev should be around */
-
-                r = sd_device_new_from_ifindex(&device, ifindex);
-                if (r < 0)
-                        return r;
-
-                r = sd_device_get_is_initialized(device);
-                if (r < 0)
-                        return r;
-                if (r == 0)
-                        /* not yet ready */
-                        return -EBUSY;
-
-                r = device_is_renaming(device);
-                if (r < 0)
-                        return r;
-                if (r > 0)
-                        /* device is under renaming */
-                        return -EBUSY;
-
-                name = net_get_persistent_name(device);
-        }
-
+        if (dev)
+                name = net_get_persistent_name(dev);
         if (name)
                 id = siphash24(name, strlen(name), HASH_KEY.bytes);
         else
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
index 8acb8c32..523dfc4a 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp-identifier.h
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include "sd-device.h"
 #include "sd-id128.h"
 
 #include "ether-addr-util.h"
@@ -66,10 +67,9 @@ int dhcp_identifier_set_duid(
                 struct duid *ret_duid,
                 size_t *ret_len);
 int dhcp_identifier_set_iaid(
-                int ifindex,
+                sd_device *dev,
                 const struct hw_addr_data *hw_addr,
                 bool legacy_unstable_byteorder,
-                bool use_mac,
                 void *ret);
 
 const char *duid_type_to_string(DUIDType t) _const_;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
index 65f6cb05..fa43f28e 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-internal.h
@@ -48,6 +48,8 @@ struct sd_dhcp6_client {
         int event_priority;
         int fd;
 
+        sd_device *dev;
+
         DHCP6State state;
         bool information_request;
         usec_t information_request_time_usec;
@@ -77,8 +79,9 @@ struct sd_dhcp6_client {
 
         sd_dhcp6_client_callback_t callback;
         void *userdata;
+        bool send_release;
 
-        /* Ignore ifindex when generating iaid. See dhcp_identifier_set_iaid(). */
+        /* Ignore machine-ID when generating DUID. See dhcp_identifier_set_duid_en(). */
         bool test_mode;
 };
 
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c
index beace4c7..7b17dbc1 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-network.c
@@ -25,7 +25,7 @@ int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) {
                 .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT),
                 .in6.sin6_scope_id = ifindex,
         };
-        _cleanup_close_ int s = -1;
+        _cleanup_close_ int s = -EBADF;
         int r;
 
         assert(ifindex > 0);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
index 14e9c806..23cf8a89 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-option.c
@@ -497,13 +497,18 @@ int dhcp6_option_parse(
 }
 
 int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_status_message) {
+        DHCP6Status status;
+
         assert(data || data_len == 0);
 
         if (data_len < sizeof(uint16_t))
                 return -EBADMSG;
 
+        status = unaligned_read_be16(data);
+
         if (ret_status_message) {
-                char *msg;
+                _cleanup_free_ char *msg = NULL;
+                const char *s;
 
                 /* The status message MUST NOT be null-terminated. See section 21.13 of RFC8415.
                  * Let's escape unsafe characters for safety. */
@@ -511,10 +516,14 @@ int dhcp6_option_parse_status(const uint8_t *data, size_t data_len, char **ret_s
                 if (!msg)
                         return -ENOMEM;
 
-                *ret_status_message = msg;
+                s = dhcp6_message_status_to_string(status);
+                if (s && !strextend_with_separator(&msg, ": ", s))
+                        return -ENOMEM;
+
+                *ret_status_message = TAKE_PTR(msg);
         }
 
-        return unaligned_read_be16(data);
+        return status;
 }
 
 static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t *buf, size_t buflen) {
@@ -540,9 +549,8 @@ static int dhcp6_option_parse_ia_options(sd_dhcp6_client *client, const uint8_t
                                 return r;
                         if (r > 0)
                                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                              "Received an IA address or PD prefix option with non-zero status: %s%s%s",
-                                                              strempty(msg), isempty(msg) ? "" : ": ",
-                                                              dhcp6_message_status_to_string(r));
+                                                              "Received an IA address or PD prefix option with non-zero status%s%s",
+                                                              isempty(msg) ? "." : ": ", strempty(msg));
                         if (r < 0)
                                 /* Let's log but ignore the invalid status option. */
                                 log_dhcp6_client_errno(client, r,
@@ -748,9 +756,8 @@ int dhcp6_option_parse_ia(
                                 return r;
                         if (r > 0)
                                 return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL),
-                                                              "Received an IA option with non-zero status: %s%s%s",
-                                                              strempty(msg), isempty(msg) ? "" : ": ",
-                                                              dhcp6_message_status_to_string(r));
+                                                              "Received an IA option with non-zero status%s%s",
+                                                              isempty(msg) ? "." : ": ", strempty(msg));
                         if (r < 0)
                                 log_dhcp6_client_errno(client, r,
                                                        "Received an IA option with an invalid status sub option, ignoring: %m");
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c
index 4e945777..059930eb 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.c
@@ -13,6 +13,7 @@ static const char * const dhcp6_state_table[_DHCP6_STATE_MAX] = {
         [DHCP6_STATE_BOUND]               = "bound",
         [DHCP6_STATE_RENEW]               = "renew",
         [DHCP6_STATE_REBIND]              = "rebind",
+        [DHCP6_STATE_STOPPING]            = "stopping",
 };
 
 DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dhcp6_state, DHCP6State);
diff --git a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h
index 18217691..c70f9320 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h
+++ b/src/libnm-systemd-core/src/libsystemd-network/dhcp6-protocol.h
@@ -58,6 +58,7 @@ typedef enum DHCP6State {
         DHCP6_STATE_BOUND,
         DHCP6_STATE_RENEW,
         DHCP6_STATE_REBIND,
+        DHCP6_STATE_STOPPING,
         _DHCP6_STATE_MAX,
         _DHCP6_STATE_INVALID = -EINVAL,
 } DHCP6State;
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c
index db159660..a2605b71 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-client.c
@@ -17,6 +17,7 @@
 #include "sd-dhcp6-client.h"
 
 #include "alloc-util.h"
+#include "device-util.h"
 #include "dhcp-identifier.h"
 #include "dhcp6-internal.h"
 #include "dhcp6-lease-internal.h"
@@ -309,9 +310,8 @@ static int client_ensure_iaid(sd_dhcp6_client *client) {
         if (client->iaid_set)
                 return 0;
 
-        r = dhcp_identifier_set_iaid(client->ifindex, &client->hw_addr,
+        r = dhcp_identifier_set_iaid(client->dev, &client->hw_addr,
                                      /* legacy_unstable_byteorder = */ true,
-                                     /* use_mac = */ client->test_mode,
                                      &iaid);
         if (r < 0)
                 return r;
@@ -508,6 +508,14 @@ int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) {
         return 0;
 }
 
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) {
+        assert_return(client, -EINVAL);
+        assert_return(!sd_dhcp6_client_is_running(client), -EBUSY);
+
+        client->send_release = enable;
+        return 0;
+}
+
 int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) {
         assert_return(client, -EINVAL);
 
@@ -596,7 +604,8 @@ static int client_append_common_options_in_managed_mode(
                       DHCP6_STATE_SOLICITATION,
                       DHCP6_STATE_REQUEST,
                       DHCP6_STATE_RENEW,
-                      DHCP6_STATE_REBIND));
+                      DHCP6_STATE_REBIND,
+                      DHCP6_STATE_STOPPING));
         assert(buf);
         assert(*buf);
         assert(offset);
@@ -613,9 +622,11 @@ static int client_append_common_options_in_managed_mode(
                         return r;
         }
 
-        r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
-        if (r < 0)
-                return r;
+        if (client->state != DHCP6_STATE_STOPPING) {
+                r = dhcp6_option_append_fqdn(buf, offset, client->fqdn);
+                if (r < 0)
+                        return r;
+        }
 
         r = dhcp6_option_append_user_class(buf, offset, client->user_class);
         if (r < 0)
@@ -646,6 +657,8 @@ static DHCP6MessageType client_message_type_from_state(sd_dhcp6_client *client)
                 return DHCP6_MESSAGE_RENEW;
         case DHCP6_STATE_REBIND:
                 return DHCP6_MESSAGE_REBIND;
+        case DHCP6_STATE_STOPPING:
+                return DHCP6_MESSAGE_RELEASE;
         default:
                 assert_not_reached();
         }
@@ -689,6 +702,9 @@ static int client_append_oro(sd_dhcp6_client *client, uint8_t **buf, size_t *off
                 req_opts = p;
                 break;
 
+        case DHCP6_STATE_STOPPING:
+                return 0;
+
         default:
                 n = client->n_req_opts;
                 req_opts = client->req_opts;
@@ -700,6 +716,22 @@ static int client_append_oro(sd_dhcp6_client *client, uint8_t **buf, size_t *off
         return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_ORO, n * sizeof(be16_t), req_opts);
 }
 
+static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t *offset) {
+        assert(client);
+        assert(buf);
+        assert(*buf);
+        assert(offset);
+
+        if (!client->mudurl)
+                return 0;
+
+        if (client->state == DHCP6_STATE_STOPPING)
+                return 0;
+
+        return dhcp6_option_append(buf, offset, SD_DHCP6_OPTION_MUD_URL_V6,
+                                   strlen(client->mudurl), client->mudurl);
+}
+
 int dhcp6_client_send_message(sd_dhcp6_client *client) {
         _cleanup_free_ uint8_t *buf = NULL;
         struct in6_addr all_servers =
@@ -745,7 +777,7 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
 
         case DHCP6_STATE_REQUEST:
         case DHCP6_STATE_RENEW:
-
+        case DHCP6_STATE_STOPPING:
                 r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_SERVERID,
                                         client->lease->serverid_len,
                                         client->lease->serverid);
@@ -763,18 +795,15 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) {
                         return r;
                 break;
 
-        case DHCP6_STATE_STOPPED:
         case DHCP6_STATE_BOUND:
+        case DHCP6_STATE_STOPPED:
         default:
                 assert_not_reached();
         }
 
-        if (client->mudurl) {
-                r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_MUD_URL_V6,
-                                        strlen(client->mudurl), client->mudurl);
-                if (r < 0)
-                        return r;
-        }
+        r = client_append_mudurl(client, &buf, &offset);
+        if (r < 0)
+                return r;
 
         r = client_append_oro(client, &buf, &offset);
         if (r < 0)
@@ -866,6 +895,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                 break;
 
         case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_STOPPING:
         case DHCP6_STATE_BOUND:
         default:
                 assert_not_reached();
@@ -921,6 +951,7 @@ static int client_start_transaction(sd_dhcp6_client *client, DHCP6State state) {
                 assert(IN_SET(client->state, DHCP6_STATE_BOUND, DHCP6_STATE_RENEW));
                 break;
         case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_STOPPING:
         case DHCP6_STATE_BOUND:
         default:
                 assert_not_reached();
@@ -1255,7 +1286,6 @@ static int client_receive_message(
                 .msg_control = &control,
                 .msg_controllen = sizeof(control),
         };
-        struct cmsghdr *cmsg;
         triple_timestamp t = {};
         _cleanup_free_ DHCP6Message *message = NULL;
         struct in6_addr *server_address = NULL;
@@ -1299,12 +1329,9 @@ static int client_receive_message(
                 server_address = &sa.in6.sin6_addr;
         }
 
-        CMSG_FOREACH(cmsg, &msg) {
-                if (cmsg->cmsg_level == SOL_SOCKET &&
-                    cmsg->cmsg_type == SO_TIMESTAMP &&
-                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval)))
-                        triple_timestamp_from_realtime(&t, timeval_load((struct timeval*) (void *) CMSG_DATA(cmsg)));
-        }
+        struct timeval *tv = CMSG_FIND_AND_COPY_DATA(&msg, SOL_SOCKET, SCM_TIMESTAMP, struct timeval);
+        if (tv)
+                triple_timestamp_from_realtime(&t, timeval_load(tv));
 
         if (client->transaction_id != (message->transaction_id & htobe32(0x00ffffff)))
                 return 0;
@@ -1329,6 +1356,7 @@ static int client_receive_message(
 
         case DHCP6_STATE_BOUND:
         case DHCP6_STATE_STOPPED:
+        case DHCP6_STATE_STOPPING:
         default:
                 assert_not_reached();
         }
@@ -1336,10 +1364,37 @@ static int client_receive_message(
         return 0;
 }
 
+static int client_send_release(sd_dhcp6_client *client) {
+        sd_dhcp6_lease *lease;
+
+        assert(client);
+
+        if (!client->send_release)
+                return 0;
+
+        if (sd_dhcp6_client_get_lease(client, &lease) < 0)
+                return 0;
+
+        if (!lease->ia_na && !lease->ia_pd)
+                return 0;
+
+        client_set_state(client, DHCP6_STATE_STOPPING);
+        return dhcp6_client_send_message(client);
+}
+
 int sd_dhcp6_client_stop(sd_dhcp6_client *client) {
+        int r;
+
         if (!client)
                 return 0;
 
+        /* Intentionally ignoring failure to send DHCP6 release. The DHCPv6 client
+         * engine is about to release its UDP socket unconditionally. */
+        r = client_send_release(client);
+        if (r < 0)
+                log_dhcp6_client_errno(client, r,
+                                       "Failed to send DHCP6 release message, ignoring: %m");
+
         client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP);
 
         client->receive_message = sd_event_source_unref(client->receive_message);
@@ -1456,6 +1511,12 @@ sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) {
         return client->event;
 }
 
+int sd_dhcp6_client_attach_device(sd_dhcp6_client *client, sd_device *dev) {
+        assert_return(client, -EINVAL);
+
+        return device_unref_and_replace(client->dev, dev);
+}
+
 static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
         if (!client)
                 return NULL;
@@ -1471,6 +1532,8 @@ static sd_dhcp6_client *dhcp6_client_free(sd_dhcp6_client *client) {
 
         client->fd = safe_close(client->fd);
 
+        sd_device_unref(client->dev);
+
         free(client->req_opts);
         free(client->fqdn);
         free(client->mudurl);
@@ -1501,7 +1564,7 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret) {
                 .ia_pd.type = SD_DHCP6_OPTION_IA_PD,
                 .ifindex = -1,
                 .request_ia = DHCP6_REQUEST_IA_NA | DHCP6_REQUEST_IA_PD,
-                .fd = -1,
+                .fd = -EBADF,
                 .rapid_commit = true,
         };
 
diff --git a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c
index 5dcd2eb2..3e25b4e8 100644
--- a/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c
+++ b/src/libnm-systemd-core/src/libsystemd-network/sd-dhcp6-lease.c
@@ -512,13 +512,11 @@ static int dhcp6_lease_parse_message(
                         r = dhcp6_option_parse_status(optval, optlen, &msg);
                         if (r < 0)
                                 return log_dhcp6_client_errno(client, r, "Failed to parse status code: %m");
-
                         if (r > 0)
                                 return log_dhcp6_client_errno(client, dhcp6_message_status_to_errno(r),
-                                                              "Received %s message with non-zero status: %s%s%s",
+                                                              "Received %s message with non-zero status%s%s",
                                                               dhcp6_message_type_to_string(message->type),
-                                                              strempty(msg), isempty(msg) ? "" : ": ",
-                                                              dhcp6_message_status_to_string(r));
+                                                              isempty(msg) ? "." : ": ", strempty(msg));
                         break;
                 }
                 case SD_DHCP6_OPTION_IA_NA: {
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h
new file mode 100644
index 00000000..a1b5e91e
--- /dev/null
+++ b/src/libnm-systemd-core/src/libsystemd/sd-device/device-util.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "sd-device.h"
+
+#include "log.h"
+#include "macro.h"
+
+#define device_unref_and_replace(a, b)                                  \
+        unref_and_replace_full(a, b, sd_device_ref, sd_device_unref)
+
+#define FOREACH_DEVICE_PROPERTY(device, key, value)                \
+        for (key = sd_device_get_property_first(device, &(value)); \
+             key;                                                  \
+             key = sd_device_get_property_next(device, &(value)))
+
+#define FOREACH_DEVICE_TAG(device, tag)             \
+        for (tag = sd_device_get_tag_first(device); \
+             tag;                                   \
+             tag = sd_device_get_tag_next(device))
+
+#define FOREACH_DEVICE_CURRENT_TAG(device, tag)             \
+        for (tag = sd_device_get_current_tag_first(device); \
+             tag;                                   \
+             tag = sd_device_get_current_tag_next(device))
+
+#define FOREACH_DEVICE_SYSATTR(device, attr)             \
+        for (attr = sd_device_get_sysattr_first(device); \
+             attr;                                       \
+             attr = sd_device_get_sysattr_next(device))
+
+#define FOREACH_DEVICE_DEVLINK(device, devlink)             \
+        for (devlink = sd_device_get_devlink_first(device); \
+             devlink;                                   \
+             devlink = sd_device_get_devlink_next(device))
+
+#define _FOREACH_DEVICE_CHILD(device, child, suffix_ptr)                \
+        for (child = sd_device_get_child_first(device, suffix_ptr);     \
+             child;                                                     \
+             child = sd_device_get_child_next(device, suffix_ptr))
+
+#define FOREACH_DEVICE_CHILD(device, child)                             \
+        _FOREACH_DEVICE_CHILD(device, child, NULL)
+
+#define FOREACH_DEVICE_CHILD_WITH_SUFFIX(device, child, suffix)         \
+        _FOREACH_DEVICE_CHILD(device, child, &suffix)
+
+#define FOREACH_DEVICE(enumerator, device)                               \
+        for (device = sd_device_enumerator_get_device_first(enumerator); \
+             device;                                                     \
+             device = sd_device_enumerator_get_device_next(enumerator))
+
+#define FOREACH_SUBSYSTEM(enumerator, device)                               \
+        for (device = sd_device_enumerator_get_subsystem_first(enumerator); \
+             device;                                                        \
+             device = sd_device_enumerator_get_subsystem_next(enumerator))
+
+#define log_device_full_errno_zerook(device, level, error, ...)         \
+        ({                                                              \
+                const char *_sysname = NULL;                            \
+                sd_device *_d = (device);                               \
+                int _level = (level), _e = (error);                     \
+                                                                        \
+                if (_d && _unlikely_(log_get_max_level() >= LOG_PRI(_level))) \
+                        (void) sd_device_get_sysname(_d, &_sysname);    \
+                log_object_internal(_level, _e, PROJECT_FILE, __LINE__, __func__, \
+                                    _sysname ? "DEVICE=" : NULL, _sysname, \
+                                    NULL, NULL, __VA_ARGS__);           \
+        })
+
+#define log_device_full_errno(device, level, error, ...)                \
+        ({                                                              \
+                int _error = (error);                                   \
+                ASSERT_NON_ZERO(_error);                                \
+                log_device_full_errno_zerook(device, level, _error, __VA_ARGS__); \
+        })
+
+#define log_device_full(device, level, ...) (void) log_device_full_errno_zerook(device, level, 0, __VA_ARGS__)
+
+#define log_device_debug(device, ...)   log_device_full(device, LOG_DEBUG, __VA_ARGS__)
+#define log_device_info(device, ...)    log_device_full(device, LOG_INFO, __VA_ARGS__)
+#define log_device_notice(device, ...)  log_device_full(device, LOG_NOTICE, __VA_ARGS__)
+#define log_device_warning(device, ...) log_device_full(device, LOG_WARNING, __VA_ARGS__)
+#define log_device_error(device, ...)   log_device_full(device, LOG_ERR, __VA_ARGS__)
+
+#define log_device_debug_errno(device, error, ...)   log_device_full_errno(device, LOG_DEBUG, error, __VA_ARGS__)
+#define log_device_info_errno(device, error, ...)    log_device_full_errno(device, LOG_INFO, error, __VA_ARGS__)
+#define log_device_notice_errno(device, error, ...)  log_device_full_errno(device, LOG_NOTICE, error, __VA_ARGS__)
+#define log_device_warning_errno(device, error, ...) log_device_full_errno(device, LOG_WARNING, error, __VA_ARGS__)
+#define log_device_error_errno(device, error, ...)   log_device_full_errno(device, LOG_ERR, error, __VA_ARGS__)
+
+int devname_from_devnum(mode_t mode, dev_t devnum, char **ret);
+static inline int devname_from_stat_rdev(const struct stat *st, char **ret) {
+        assert(st);
+        return devname_from_devnum(st->st_mode, st->st_rdev, ret);
+}
+int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret);
+
+char** device_make_log_fields(sd_device *device);
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h b/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h
index 6092652d..f4e38d78 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h
+++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-source.h
@@ -27,6 +27,7 @@ typedef enum EventSourceType {
         SOURCE_EXIT,
         SOURCE_WATCHDOG,
         SOURCE_INOTIFY,
+        SOURCE_MEMORY_PRESSURE,
         _SOURCE_EVENT_SOURCE_TYPE_MAX,
         _SOURCE_EVENT_SOURCE_TYPE_INVALID = -EINVAL,
 } EventSourceType;
@@ -129,6 +130,17 @@ struct sd_event_source {
                         struct inode_data *inode_data;
                         LIST_FIELDS(sd_event_source, by_inode_data);
                 } inotify;
+                struct {
+                        int fd;
+                        sd_event_handler_t callback;
+                        void *write_buffer;
+                        size_t write_buffer_size;
+                        uint32_t events, revents;
+                        LIST_FIELDS(sd_event_source, write_list);
+                        bool registered:1;
+                        bool locked:1;
+                        bool in_write_list:1;
+                } memory_pressure;
         };
 };
 
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c
index ebbd4422..663296f4 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c
+++ b/src/libnm-systemd-core/src/libsystemd/sd-event/event-util.c
@@ -48,7 +48,7 @@ int event_reset_time(
                         return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "sd-event: Current clock id %i of event source \"%s\" is different from specified one %i.",
                                                (int)c,
-                                               strna((*s)->description ? : description),
+                                               strna((*s)->description ?: description),
                                                (int)clock);
 
                 r = sd_event_source_set_time(*s, usec);
@@ -114,7 +114,7 @@ int event_reset_time_relative(
 #if 0 /* NM_IGNORED */
 int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata) {
         _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         int r;
 
         assert(e);
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c
index 70776077..5c7ba182 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c
+++ b/src/libnm-systemd-core/src/libsystemd/sd-event/sd-event.c
@@ -9,6 +9,7 @@
 #include "sd-daemon.h"
 #include "sd-event.h"
 #include "sd-id128.h"
+#include "sd-messages.h"
 
 #include "alloc-util.h"
 #include "env-util.h"
@@ -17,14 +18,24 @@
 #include "fs-util.h"
 #include "glyph-util.h"
 #include "hashmap.h"
+#include "hexdecoct.h"
 #include "list.h"
+#include "logarithm.h"
 #include "macro.h"
+#include "mallinfo-util.h"
 #include "memory-util.h"
+#include "missing_magic.h"
 #include "missing_syscall.h"
+#include "missing_threads.h"
+#include "origin-id.h"
+#include "path-util.h"
 #include "prioq.h"
 #include "process-util.h"
+#include "psi-util.h"
 #include "set.h"
 #include "signal-util.h"
+#include "socket-util.h"
+#include "stat-util.h"
 #include "string-table.h"
 #include "string-util.h"
 #include "strxcpyx.h"
@@ -53,7 +64,7 @@ static bool event_source_is_offline(sd_event_source *s) {
 static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = {
         [SOURCE_IO]                  = "io",
         [SOURCE_TIME_REALTIME]       = "realtime",
-        [SOURCE_TIME_BOOTTIME]       = "bootime",
+        [SOURCE_TIME_BOOTTIME]       = "boottime",
         [SOURCE_TIME_MONOTONIC]      = "monotonic",
         [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm",
         [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm",
@@ -64,6 +75,7 @@ static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX]
         [SOURCE_EXIT]                = "exit",
         [SOURCE_WATCHDOG]            = "watchdog",
         [SOURCE_INOTIFY]             = "inotify",
+        [SOURCE_MEMORY_PRESSURE]     = "memory-pressure",
 };
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
@@ -86,7 +98,8 @@ DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int);
                SOURCE_TIME_BOOTTIME_ALARM,      \
                SOURCE_SIGNAL,                   \
                SOURCE_DEFER,                    \
-               SOURCE_INOTIFY)
+               SOURCE_INOTIFY,                  \
+               SOURCE_MEMORY_PRESSURE)
 
 /* This is used to assert that we didn't pass an unexpected source type to event_source_time_prioq_put().
  * Time sources and ratelimited sources can be passed, so effectively this is the same as the
@@ -131,7 +144,10 @@ struct sd_event {
         /* A list of inotify objects that already have events buffered which aren't processed yet */
         LIST_HEAD(struct inotify_data, buffered_inotify_data_list);
 
-        pid_t original_pid;
+        /* A list of memory pressure event sources that still need their subscription string written */
+        LIST_HEAD(sd_event_source, memory_pressure_write_list);
+
+        uint64_t origin_id;
 
         uint64_t iteration;
         triple_timestamp timestamp;
@@ -161,6 +177,8 @@ struct sd_event {
         unsigned delays[sizeof(usec_t) * 8];
 };
 
+DEFINE_PRIVATE_ORIGIN_ID_HELPERS(sd_event, event);
+
 static thread_local sd_event *default_event = NULL;
 
 static void source_disconnect(sd_event_source *s);
@@ -379,25 +397,25 @@ _public_ int sd_event_new(sd_event** ret) {
 
         *e = (sd_event) {
                 .n_ref = 1,
-                .epoll_fd = -1,
-                .watchdog_fd = -1,
+                .epoll_fd = -EBADF,
+                .watchdog_fd = -EBADF,
                 .realtime.wakeup = WAKEUP_CLOCK_DATA,
-                .realtime.fd = -1,
+                .realtime.fd = -EBADF,
                 .realtime.next = USEC_INFINITY,
                 .boottime.wakeup = WAKEUP_CLOCK_DATA,
-                .boottime.fd = -1,
+                .boottime.fd = -EBADF,
                 .boottime.next = USEC_INFINITY,
                 .monotonic.wakeup = WAKEUP_CLOCK_DATA,
-                .monotonic.fd = -1,
+                .monotonic.fd = -EBADF,
                 .monotonic.next = USEC_INFINITY,
                 .realtime_alarm.wakeup = WAKEUP_CLOCK_DATA,
-                .realtime_alarm.fd = -1,
+                .realtime_alarm.fd = -EBADF,
                 .realtime_alarm.next = USEC_INFINITY,
                 .boottime_alarm.wakeup = WAKEUP_CLOCK_DATA,
-                .boottime_alarm.fd = -1,
+                .boottime_alarm.fd = -EBADF,
                 .boottime_alarm.next = USEC_INFINITY,
                 .perturb = USEC_INFINITY,
-                .original_pid = getpid_cached(),
+                .origin_id = origin_id_query(),
         };
 
         r = prioq_ensure_allocated(&e->pending, pending_prioq_compare);
@@ -426,7 +444,31 @@ fail:
         return r;
 }
 
-DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event, sd_event, event_free);
+/* Define manually so we can add the origin check */
+_public_ sd_event *sd_event_ref(sd_event *e) {
+        if (!e)
+                return NULL;
+        if (event_origin_changed(e))
+                return NULL;
+
+        e->n_ref++;
+
+        return e;
+}
+
+_public_ sd_event* sd_event_unref(sd_event *e) {
+        if (!e)
+                return NULL;
+        if (event_origin_changed(e))
+                return NULL;
+
+        assert(e->n_ref > 0);
+        if (--e->n_ref > 0)
+                return NULL;
+
+        return event_free(e);
+}
+
 #define PROTECT_EVENT(e)                                                \
         _unused_ _cleanup_(sd_event_unrefp) sd_event *_ref = sd_event_ref(e);
 
@@ -436,20 +478,11 @@ _public_ sd_event_source* sd_event_source_disable_unref(sd_event_source *s) {
         return sd_event_source_unref(s);
 }
 
-static bool event_pid_changed(sd_event *e) {
-        assert(e);
-
-        /* We don't support people creating an event loop and keeping
-         * it around over a fork(). Let's complain. */
-
-        return e->original_pid != getpid_cached();
-}
-
 static void source_io_unregister(sd_event_source *s) {
         assert(s);
         assert(s->type == SOURCE_IO);
 
-        if (event_pid_changed(s->event))
+        if (event_origin_changed(s->event))
                 return;
 
         if (!s->io.registered)
@@ -490,7 +523,7 @@ static void source_child_pidfd_unregister(sd_event_source *s) {
         assert(s);
         assert(s->type == SOURCE_CHILD);
 
-        if (event_pid_changed(s->event))
+        if (event_origin_changed(s->event))
                 return;
 
         if (!s->child.registered)
@@ -525,6 +558,67 @@ static int source_child_pidfd_register(sd_event_source *s, int enabled) {
         return 0;
 }
 
+static void source_memory_pressure_unregister(sd_event_source *s) {
+        assert(s);
+        assert(s->type == SOURCE_MEMORY_PRESSURE);
+
+        if (event_origin_changed(s->event))
+                return;
+
+        if (!s->memory_pressure.registered)
+                return;
+
+        if (epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->memory_pressure.fd, NULL) < 0)
+                log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll, ignoring: %m",
+                                strna(s->description), event_source_type_to_string(s->type));
+
+        s->memory_pressure.registered = false;
+}
+
+static int source_memory_pressure_register(sd_event_source *s, int enabled) {
+        assert(s);
+        assert(s->type == SOURCE_MEMORY_PRESSURE);
+        assert(enabled != SD_EVENT_OFF);
+
+        struct epoll_event ev = {
+                .events = s->memory_pressure.write_buffer_size > 0 ? EPOLLOUT :
+                          (s->memory_pressure.events | (enabled == SD_EVENT_ONESHOT ? EPOLLONESHOT : 0)),
+                .data.ptr = s,
+        };
+
+        if (epoll_ctl(s->event->epoll_fd,
+                      s->memory_pressure.registered ? EPOLL_CTL_MOD : EPOLL_CTL_ADD,
+                      s->memory_pressure.fd, &ev) < 0)
+                return -errno;
+
+        s->memory_pressure.registered = true;
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+static void source_memory_pressure_add_to_write_list(sd_event_source *s) {
+        assert(s);
+        assert(s->type == SOURCE_MEMORY_PRESSURE);
+
+        if (s->memory_pressure.in_write_list)
+                return;
+
+        LIST_PREPEND(memory_pressure.write_list, s->event->memory_pressure_write_list, s);
+        s->memory_pressure.in_write_list = true;
+}
+#endif /* NM_IGNORED */
+
+static void source_memory_pressure_remove_from_write_list(sd_event_source *s) {
+        assert(s);
+        assert(s->type == SOURCE_MEMORY_PRESSURE);
+
+        if (!s->memory_pressure.in_write_list)
+                return;
+
+        LIST_REMOVE(memory_pressure.write_list, s->event->memory_pressure_write_list, s);
+        s->memory_pressure.in_write_list = false;
+}
+
 static clockid_t event_source_type_to_clock(EventSourceType t) {
 
         switch (t) {
@@ -622,7 +716,7 @@ static int event_make_signal_data(
 
         assert(e);
 
-        if (event_pid_changed(e))
+        if (event_origin_changed(e))
                 return -ECHILD;
 
         if (e->signal_sources && e->signal_sources[sig])
@@ -644,7 +738,7 @@ static int event_make_signal_data(
 
                 *d = (struct signal_data) {
                         .wakeup = WAKEUP_SIGNAL_DATA,
-                        .fd = -1,
+                        .fd = -EBADF,
                         .priority = priority,
                 };
 
@@ -660,7 +754,9 @@ static int event_make_signal_data(
         ss_copy = d->sigset;
         assert_se(sigaddset(&ss_copy, sig) >= 0);
 
-        r = signalfd(d->fd, &ss_copy, SFD_NONBLOCK|SFD_CLOEXEC);
+        r = signalfd(d->fd >= 0 ? d->fd : -1,   /* the first arg must be -1 or a valid signalfd */
+                     &ss_copy,
+                     SFD_NONBLOCK|SFD_CLOEXEC);
         if (r < 0) {
                 r = -errno;
                 goto fail;
@@ -717,7 +813,7 @@ static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig
                 return;
         }
 
-        if (event_pid_changed(e))
+        if (event_origin_changed(e))
                 return;
 
         assert(d->fd >= 0);
@@ -880,7 +976,7 @@ static void source_disconnect(sd_event_source *s) {
                 break;
 
         case SOURCE_CHILD:
-                if (event_pid_changed(s->event))
+                if (event_origin_changed(s->event))
                         s->child.process_owned = false;
 
                 if (s->child.pid > 0) {
@@ -946,6 +1042,11 @@ static void source_disconnect(sd_event_source *s) {
                 break;
         }
 
+        case SOURCE_MEMORY_PRESSURE:
+                source_memory_pressure_remove_from_write_list(s);
+                source_memory_pressure_unregister(s);
+                break;
+
         default:
                 assert_not_reached();
         }
@@ -1016,6 +1117,11 @@ static sd_event_source* source_free(sd_event_source *s) {
                         s->child.pidfd = safe_close(s->child.pidfd);
         }
 
+        if (s->type == SOURCE_MEMORY_PRESSURE) {
+                s->memory_pressure.fd = safe_close(s->memory_pressure.fd);
+                s->memory_pressure.write_buffer = mfree(s->memory_pressure.write_buffer);
+        }
+
         if (s->destroy_callback)
                 s->destroy_callback(s->userdata);
 
@@ -1074,22 +1180,48 @@ static int source_set_pending(sd_event_source *s, bool b) {
 }
 
 static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) {
+
+        /* Let's allocate exactly what we need. Note that the difference of the smallest event source
+         * structure to the largest is 144 bytes on x86-64 at the time of writing, i.e. more than two cache
+         * lines. */
+        static const size_t size_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = {
+                [SOURCE_IO]                  = endoffsetof_field(sd_event_source, io),
+                [SOURCE_TIME_REALTIME]       = endoffsetof_field(sd_event_source, time),
+                [SOURCE_TIME_BOOTTIME]       = endoffsetof_field(sd_event_source, time),
+                [SOURCE_TIME_MONOTONIC]      = endoffsetof_field(sd_event_source, time),
+                [SOURCE_TIME_REALTIME_ALARM] = endoffsetof_field(sd_event_source, time),
+                [SOURCE_TIME_BOOTTIME_ALARM] = endoffsetof_field(sd_event_source, time),
+                [SOURCE_SIGNAL]              = endoffsetof_field(sd_event_source, signal),
+                [SOURCE_CHILD]               = endoffsetof_field(sd_event_source, child),
+                [SOURCE_DEFER]               = endoffsetof_field(sd_event_source, defer),
+                [SOURCE_POST]                = endoffsetof_field(sd_event_source, post),
+                [SOURCE_EXIT]                = endoffsetof_field(sd_event_source, exit),
+                [SOURCE_INOTIFY]             = endoffsetof_field(sd_event_source, inotify),
+                [SOURCE_MEMORY_PRESSURE]     = endoffsetof_field(sd_event_source, memory_pressure),
+        };
+
         sd_event_source *s;
 
         assert(e);
+        assert(type >= 0);
+        assert(type < _SOURCE_EVENT_SOURCE_TYPE_MAX);
+        assert(size_table[type] > 0);
 
-        s = new(sd_event_source, 1);
+        s = malloc0(size_table[type]);
         if (!s)
                 return NULL;
-
-        *s = (struct sd_event_source) {
-                .n_ref = 1,
-                .event = e,
-                .floating = floating,
-                .type = type,
-                .pending_index = PRIOQ_IDX_NULL,
-                .prepare_index = PRIOQ_IDX_NULL,
-        };
+        /* We use expand_to_usable() here to tell gcc that it should consider this an object of the full
+         * size, even if we only allocate the initial part we need. */
+        s = expand_to_usable(s, sizeof(sd_event_source));
+
+        /* Note: we cannot use compound initialization here, because sizeof(sd_event_source) is likely larger
+         * than what we allocated here. */
+        s->n_ref = 1;
+        s->event = e;
+        s->floating = floating;
+        s->type = type;
+        s->pending_index = PRIOQ_IDX_NULL;
+        s->prepare_index = PRIOQ_IDX_NULL;
 
         if (!floating)
                 sd_event_ref(e);
@@ -1122,7 +1254,7 @@ _public_ int sd_event_add_io(
         assert_return(fd >= 0, -EBADF);
         assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = io_exit_callback;
@@ -1150,22 +1282,21 @@ _public_ int sd_event_add_io(
 }
 
 static void initialize_perturb(sd_event *e) {
-        sd_id128_t bootid = {};
+        sd_id128_t id = {};
 
-        /* When we sleep for longer, we try to realign the wakeup to
-           the same time within each minute/second/250ms, so that
-           events all across the system can be coalesced into a single
-           CPU wakeup. However, let's take some system-specific
-           randomness for this value, so that in a network of systems
-           with synced clocks timer events are distributed a
-           bit. Here, we calculate a perturbation usec offset from the
-           boot ID. */
+        /* When we sleep for longer, we try to realign the wakeup to the same time within each
+         * minute/second/250ms, so that events all across the system can be coalesced into a single CPU
+         * wakeup. However, let's take some system-specific randomness for this value, so that in a network
+         * of systems with synced clocks timer events are distributed a bit. Here, we calculate a
+         * perturbation usec offset from the boot ID (or machine ID if failed, e.g. /proc is not mounted). */
 
         if (_likely_(e->perturb != USEC_INFINITY))
                 return;
 
-        if (sd_id128_get_boot(&bootid) >= 0)
-                e->perturb = (bootid.qwords[0] ^ bootid.qwords[1]) % USEC_PER_MINUTE;
+        if (sd_id128_get_boot(&id) >= 0 || sd_id128_get_machine(&id) >= 0)
+                e->perturb = (id.qwords[0] ^ id.qwords[1]) % USEC_PER_MINUTE;
+        else
+                e->perturb = 0; /* This is a super early process without /proc and /etc ?? */
 }
 
 static int event_setup_timer_fd(
@@ -1179,7 +1310,7 @@ static int event_setup_timer_fd(
         if (_likely_(d->fd >= 0))
                 return 0;
 
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC);
         if (fd < 0)
@@ -1270,7 +1401,7 @@ _public_ int sd_event_add_time(
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(accuracy != UINT64_MAX, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!clock_supported(clock)) /* Checks whether the kernel supports the clock */
                 return -EOPNOTSUPP;
@@ -1358,7 +1489,7 @@ _public_ int sd_event_add_signal(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         /* Let's make sure our special flag stays outside of the valid signal range */
         assert_cc(_NSIG < SD_EVENT_SIGNAL_PROCMASK);
@@ -1468,7 +1599,7 @@ _public_ int sd_event_add_child(
         assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
         assert_return(options != 0, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = child_exit_callback;
@@ -1516,7 +1647,7 @@ _public_ int sd_event_add_child(
                 } else
                         s->child.pidfd_owned = true; /* If we allocate the pidfd we own it by default */
         } else
-                s->child.pidfd = -1;
+                s->child.pidfd = -EBADF;
 
         if (EVENT_SOURCE_WATCH_PIDFD(s)) {
                 /* We have a pidfd and we only want to watch for exit */
@@ -1566,7 +1697,7 @@ _public_ int sd_event_add_child_pidfd(
         assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL);
         assert_return(options != 0, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = child_exit_callback;
@@ -1648,7 +1779,7 @@ _public_ int sd_event_add_defer(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = generic_exit_callback;
@@ -1684,7 +1815,7 @@ _public_ int sd_event_add_post(
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = generic_exit_callback;
@@ -1722,7 +1853,7 @@ _public_ int sd_event_add_exit(
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(callback, -EINVAL);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         r = prioq_ensure_allocated(&e->exit, exit_prioq_compare);
         if (r < 0)
@@ -1748,6 +1879,262 @@ _public_ int sd_event_add_exit(
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
+_public_ int sd_event_trim_memory(void) {
+        int r;
+
+        /* A default implementation of a memory pressure callback. Simply releases our own allocation caches
+         * and glibc's. This is automatically used when people call sd_event_add_memory_pressure() with a
+         * NULL callback parameter. */
+
+        log_debug("Memory pressure event, trimming malloc() memory.");
+
+#if HAVE_GENERIC_MALLINFO
+        generic_mallinfo before_mallinfo = generic_mallinfo_get();
+#endif
+
+        usec_t before_timestamp = now(CLOCK_MONOTONIC);
+        hashmap_trim_pools();
+        r = malloc_trim(0);
+        usec_t after_timestamp = now(CLOCK_MONOTONIC);
+
+        if (r > 0)
+                log_debug("Successfully trimmed some memory.");
+        else
+                log_debug("Couldn't trim any memory.");
+
+        usec_t period = after_timestamp - before_timestamp;
+
+#if HAVE_GENERIC_MALLINFO
+        generic_mallinfo after_mallinfo = generic_mallinfo_get();
+        size_t l = LESS_BY((size_t) before_mallinfo.hblkhd, (size_t) after_mallinfo.hblkhd) +
+                LESS_BY((size_t) before_mallinfo.arena, (size_t) after_mallinfo.arena);
+        log_struct(LOG_DEBUG,
+                   LOG_MESSAGE("Memory trimming took %s, returned %s to OS.",
+                               FORMAT_TIMESPAN(period, 0),
+                               FORMAT_BYTES(l)),
+                   "MESSAGE_ID=" SD_MESSAGE_MEMORY_TRIM_STR,
+                   "TRIMMED_BYTES=%zu", l,
+                   "TRIMMED_USEC=" USEC_FMT, period);
+#else
+        log_struct(LOG_DEBUG,
+                   LOG_MESSAGE("Memory trimming took %s.",
+                               FORMAT_TIMESPAN(period, 0)),
+                   "MESSAGE_ID=" SD_MESSAGE_MEMORY_TRIM_STR,
+                   "TRIMMED_USEC=" USEC_FMT, period);
+#endif
+
+        return 0;
+}
+
+static int memory_pressure_callback(sd_event_source *s, void *userdata) {
+        assert(s);
+
+        sd_event_trim_memory();
+        return 0;
+}
+
+_public_ int sd_event_add_memory_pressure(
+                sd_event *e,
+                sd_event_source **ret,
+                sd_event_handler_t callback,
+                void *userdata) {
+
+        _cleanup_free_ char *w = NULL;
+        _cleanup_(source_freep) sd_event_source *s = NULL;
+        _cleanup_close_ int path_fd = -EBADF, fd = -EBADF;
+        _cleanup_free_ void *write_buffer = NULL;
+        const char *watch, *watch_fallback = NULL, *env;
+        size_t write_buffer_size = 0;
+        struct stat st;
+        uint32_t events;
+        bool locked;
+        int r;
+
+        assert_return(e, -EINVAL);
+        assert_return(e = event_resolve(e), -ENOPKG);
+        assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
+        assert_return(!event_origin_changed(e), -ECHILD);
+
+        if (!callback)
+                callback = memory_pressure_callback;
+
+        s = source_new(e, !ret, SOURCE_MEMORY_PRESSURE);
+        if (!s)
+                return -ENOMEM;
+
+        s->wakeup = WAKEUP_EVENT_SOURCE;
+        s->memory_pressure.callback = callback;
+        s->userdata = userdata;
+        s->enabled = SD_EVENT_ON;
+        s->memory_pressure.fd = -EBADF;
+
+        env = secure_getenv("MEMORY_PRESSURE_WATCH");
+        if (env) {
+                if (isempty(env) || path_equal(env, "/dev/null"))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EHOSTDOWN),
+                                               "Memory pressure logic is explicitly disabled via $MEMORY_PRESSURE_WATCH.");
+
+                if (!path_is_absolute(env) || !path_is_normalized(env))
+                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                               "$MEMORY_PRESSURE_WATCH set to invalid path: %s", env);
+
+                watch = env;
+
+                env = secure_getenv("MEMORY_PRESSURE_WRITE");
+                if (env) {
+                        r = unbase64mem(env, SIZE_MAX, &write_buffer, &write_buffer_size);
+                        if (r < 0)
+                                return r;
+                }
+
+                locked = true;
+        } else {
+
+                r = is_pressure_supported();
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EOPNOTSUPP;
+
+                /* By default we want to watch memory pressure on the local cgroup, but we'll fall back on
+                 * the system wide pressure if for some reason we cannot (which could be: memory controller
+                 * not delegated to us, or PSI simply not available in the kernel). On legacy cgroupv1 we'll
+                 * only use the system-wide logic. */
+                r = cg_all_unified();
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        watch = "/proc/pressure/memory";
+                else {
+                        _cleanup_free_ char *cg = NULL;
+
+                        r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cg);
+                        if (r < 0)
+                                return r;
+
+                        w = path_join("/sys/fs/cgroup", cg, "memory.pressure");
+                        if (!w)
+                                return -ENOMEM;
+
+                        watch = w;
+                        watch_fallback = "/proc/pressure/memory";
+                }
+
+                /* Android uses three levels in its userspace low memory killer logic:
+                 *     some  70000 1000000
+                 *     some 100000 1000000
+                 *     full  70000 1000000
+                 *
+                 * GNOME's low memory monitor uses:
+                 *     some  70000 1000000
+                 *     some 100000 1000000
+                 *     full 100000 1000000
+                 *
+                 * We'll default to the middle level that both agree on. Except we do it on a 2s window
+                 * (i.e. 200ms per 2s, rather than 100ms per 1s), because that's the window duration the
+                 * kernel will allow us to do unprivileged, also in the future. */
+                if (asprintf((char**) &write_buffer,
+                             "%s " USEC_FMT " " USEC_FMT,
+                             MEMORY_PRESSURE_DEFAULT_TYPE,
+                             MEMORY_PRESSURE_DEFAULT_THRESHOLD_USEC,
+                             MEMORY_PRESSURE_DEFAULT_WINDOW_USEC) < 0)
+                        return -ENOMEM;
+
+                write_buffer_size = strlen(write_buffer) + 1;
+                locked = false;
+        }
+
+        path_fd = open(watch, O_PATH|O_CLOEXEC);
+        if (path_fd < 0) {
+                if (errno != ENOENT)
+                        return -errno;
+
+                /* We got ENOENT. Three options now: try the fallback if we have one, or return the error as
+                 * is (if based on user/env config), or return -EOPNOTSUPP (because we picked the path, and
+                 * the PSI service apparently is not supported) */
+                if (!watch_fallback)
+                        return locked ? -ENOENT : -EOPNOTSUPP;
+
+                path_fd = open(watch_fallback, O_PATH|O_CLOEXEC);
+                if (path_fd < 0) {
+                        if (errno == ENOENT) /* PSI is not available in the kernel even under the fallback path? */
+                                return -EOPNOTSUPP;
+                        return -errno;
+                }
+        }
+
+        if (fstat(path_fd, &st) < 0)
+                return -errno;
+
+        if (S_ISSOCK(st.st_mode)) {
+                fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+                if (fd < 0)
+                        return -errno;
+
+                r = connect_unix_path(fd, path_fd, NULL);
+                if (r < 0)
+                        return r;
+
+                events = EPOLLIN;
+
+        } else if (S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode) || S_ISCHR(st.st_mode)) {
+                fd = fd_reopen(path_fd, (write_buffer_size > 0 ? O_RDWR : O_RDONLY) |O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
+                if (fd < 0)
+                        return fd;
+
+                if (S_ISREG(st.st_mode)) {
+                        struct statfs sfs;
+
+                        /* If this is a regular file validate this is a procfs or cgroupfs file, where we look for EPOLLPRI */
+
+                        if (fstatfs(fd, &sfs) < 0)
+                                return -errno;
+
+                        if (!is_fs_type(&sfs, PROC_SUPER_MAGIC) &&
+                            !is_fs_type(&sfs, CGROUP2_SUPER_MAGIC))
+                                return -ENOTTY;
+
+                        events = EPOLLPRI;
+                } else
+                        /* For fifos and char devices just watch for EPOLLIN */
+                        events = EPOLLIN;
+
+        } else if (S_ISDIR(st.st_mode))
+                return -EISDIR;
+        else
+                return -EBADF;
+
+        s->memory_pressure.fd = TAKE_FD(fd);
+        s->memory_pressure.write_buffer = TAKE_PTR(write_buffer);
+        s->memory_pressure.write_buffer_size = write_buffer_size;
+        s->memory_pressure.events = events;
+        s->memory_pressure.locked = locked;
+
+        /* So here's the thing: if we are talking to PSI we need to write the watch string before adding the
+         * fd to epoll (if we ignore this, then the watch won't work). Hence we'll not actually register the
+         * fd with the epoll right-away. Instead, we just add the event source to a list of memory pressure
+         * event sources on which writes must be executed before the first event loop iteration is
+         * executed. (We could also write the data here, right away, but we want to give the caller the
+         * freedom to call sd_event_source_set_memory_pressure_type() and
+         * sd_event_source_set_memory_pressure_rate() before we write it. */
+
+        if (s->memory_pressure.write_buffer_size > 0)
+                source_memory_pressure_add_to_write_list(s);
+        else {
+                r = source_memory_pressure_register(s, s->enabled);
+                if (r < 0)
+                        return r;
+        }
+
+        if (ret)
+                *ret = s;
+        TAKE_PTR(s);
+
+        return 0;
+}
+#endif /* NM_IGNORED */
+
 static void event_free_inotify_data(sd_event *e, struct inotify_data *d) {
         assert(e);
 
@@ -1766,7 +2153,7 @@ static void event_free_inotify_data(sd_event *e, struct inotify_data *d) {
         assert_se(hashmap_remove(e->inotify_data, &d->priority) == d);
 
         if (d->fd >= 0) {
-                if (!event_pid_changed(e) &&
+                if (!event_origin_changed(e) &&
                     epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, d->fd, NULL) < 0)
                         log_debug_errno(errno, "Failed to remove inotify fd from epoll, ignoring: %m");
 
@@ -1780,7 +2167,7 @@ static int event_make_inotify_data(
                 int64_t priority,
                 struct inotify_data **ret) {
 
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         struct inotify_data *d;
         int r;
 
@@ -1877,7 +2264,7 @@ static void event_free_inode_data(
         if (d->inotify_data) {
 
                 if (d->wd >= 0) {
-                        if (d->inotify_data->fd >= 0 && !event_pid_changed(e)) {
+                        if (d->inotify_data->fd >= 0 && !event_origin_changed(e)) {
                                 /* So here's a problem. At the time this runs the watch descriptor might already be
                                  * invalidated, because an IN_IGNORED event might be queued right the moment we enter
                                  * the syscall. Hence, whenever we get EINVAL, ignore it entirely, since it's a very
@@ -1977,7 +2364,7 @@ static int event_make_inode_data(
                 .dev = dev,
                 .ino = ino,
                 .wd = -1,
-                .fd = -1,
+                .fd = -EBADF,
                 .inotify_data = inotify_data,
         };
 
@@ -2073,7 +2460,7 @@ static int event_add_inotify_fd_internal(
                 sd_event_inotify_handler_t callback,
                 void *userdata) {
 
-        _cleanup_close_ int donated_fd = donate ? fd : -1;
+        _cleanup_close_ int donated_fd = donate ? fd : -EBADF;
         _cleanup_(source_freep) sd_event_source *s = NULL;
         struct inotify_data *inotify_data = NULL;
         struct inode_data *inode_data = NULL;
@@ -2084,7 +2471,7 @@ static int event_add_inotify_fd_internal(
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(fd >= 0, -EBADF);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!callback)
                 callback = inotify_exit_callback;
@@ -2175,9 +2562,9 @@ _public_ int sd_event_add_inotify(
 
         assert_return(path, -EINVAL);
 
-        fd = open(path, O_PATH|O_CLOEXEC|
-                  (mask & IN_ONLYDIR ? O_DIRECTORY : 0)|
-                  (mask & IN_DONT_FOLLOW ? O_NOFOLLOW : 0));
+        fd = open(path, O_PATH | O_CLOEXEC |
+                        (mask & IN_ONLYDIR ? O_DIRECTORY : 0) |
+                        (mask & IN_DONT_FOLLOW ? O_NOFOLLOW : 0));
         if (fd < 0)
                 return -errno;
 
@@ -2217,7 +2604,7 @@ DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_event_source, sd_event_source, event_sou
 
 _public_ int sd_event_source_set_description(sd_event_source *s, const char *description) {
         assert_return(s, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return free_and_strdup(&s->description, description);
 }
@@ -2225,7 +2612,6 @@ _public_ int sd_event_source_set_description(sd_event_source *s, const char *des
 _public_ int sd_event_source_get_description(sd_event_source *s, const char **description) {
         assert_return(s, -EINVAL);
         assert_return(description, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
 
         if (!s->description)
                 return -ENXIO;
@@ -2236,6 +2622,7 @@ _public_ int sd_event_source_get_description(sd_event_source *s, const char **de
 
 _public_ sd_event *sd_event_source_get_event(sd_event_source *s) {
         assert_return(s, NULL);
+        assert_return(!event_origin_changed(s->event), NULL);
 
         return s->event;
 }
@@ -2244,7 +2631,7 @@ _public_ int sd_event_source_get_pending(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->pending;
 }
@@ -2252,7 +2639,7 @@ _public_ int sd_event_source_get_pending(sd_event_source *s) {
 _public_ int sd_event_source_get_io_fd(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->io.fd;
 }
@@ -2263,7 +2650,7 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
         assert_return(s, -EINVAL);
         assert_return(fd >= 0, -EBADF);
         assert_return(s->type == SOURCE_IO, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->io.fd == fd)
                 return 0;
@@ -2296,6 +2683,7 @@ _public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) {
 _public_ int sd_event_source_get_io_fd_own(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->io.owned;
 }
@@ -2303,6 +2691,7 @@ _public_ int sd_event_source_get_io_fd_own(sd_event_source *s) {
 _public_ int sd_event_source_set_io_fd_own(sd_event_source *s, int own) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         s->io.owned = own;
         return 0;
@@ -2312,7 +2701,7 @@ _public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events)
         assert_return(s, -EINVAL);
         assert_return(events, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *events = s->io.events;
         return 0;
@@ -2325,7 +2714,7 @@ _public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events)
         assert_return(s->type == SOURCE_IO, -EDOM);
         assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         /* edge-triggered updates are never skipped, so we can reset edges */
         if (s->io.events == events && !(events & EPOLLET))
@@ -2351,7 +2740,7 @@ _public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revent
         assert_return(revents, -EINVAL);
         assert_return(s->type == SOURCE_IO, -EDOM);
         assert_return(s->pending, -ENODATA);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *revents = s->io.revents;
         return 0;
@@ -2360,14 +2749,14 @@ _public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revent
 _public_ int sd_event_source_get_signal(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_SIGNAL, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->signal.sig;
 }
 
 _public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) {
         assert_return(s, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *priority = s->priority;
         return 0;
@@ -2381,7 +2770,7 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority)
 
         assert_return(s, -EINVAL);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->priority == priority)
                 return 0;
@@ -2481,7 +2870,7 @@ _public_ int sd_event_source_get_enabled(sd_event_source *s, int *ret) {
                 return false;
 
         assert_return(s, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (ret)
                 *ret = s->enabled;
@@ -2539,6 +2928,10 @@ static int event_source_offline(
                 prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index);
                 break;
 
+        case SOURCE_MEMORY_PRESSURE:
+                source_memory_pressure_unregister(s);
+                break;
+
         case SOURCE_TIME_REALTIME:
         case SOURCE_TIME_BOOTTIME:
         case SOURCE_TIME_MONOTONIC:
@@ -2626,6 +3019,13 @@ static int event_source_online(
                         s->event->n_online_child_sources++;
                 break;
 
+        case SOURCE_MEMORY_PRESSURE:
+                r = source_memory_pressure_register(s, enabled);
+                if (r < 0)
+                        return r;
+
+                break;
+
         case SOURCE_TIME_REALTIME:
         case SOURCE_TIME_BOOTTIME:
         case SOURCE_TIME_MONOTONIC:
@@ -2664,7 +3064,7 @@ _public_ int sd_event_source_set_enabled(sd_event_source *s, int m) {
                 return 0;
 
         assert_return(s, -EINVAL);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         /* If we are dead anyway, we are fine with turning off sources, but everything else needs to fail. */
         if (s->event->state == SD_EVENT_FINISHED)
@@ -2696,7 +3096,7 @@ _public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) {
         assert_return(s, -EINVAL);
         assert_return(usec, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *usec = s->time.next;
         return 0;
@@ -2708,7 +3108,7 @@ _public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) {
         assert_return(s, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         r = source_set_pending(s, false);
         if (r < 0)
@@ -2726,6 +3126,10 @@ _public_ int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec
 
         assert_return(s, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
+
+        if (usec == USEC_INFINITY)
+                return sd_event_source_set_time(s, USEC_INFINITY);
 
         r = sd_event_now(s->event, event_source_type_to_clock(s->type), &t);
         if (r < 0)
@@ -2742,7 +3146,7 @@ _public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *use
         assert_return(s, -EINVAL);
         assert_return(usec, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *usec = s->time.accuracy;
         return 0;
@@ -2755,7 +3159,7 @@ _public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec
         assert_return(usec != UINT64_MAX, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         r = source_set_pending(s, false);
         if (r < 0)
@@ -2774,7 +3178,7 @@ _public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock
         assert_return(s, -EINVAL);
         assert_return(clock, -EINVAL);
         assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *clock = event_source_type_to_clock(s->type);
         return 0;
@@ -2784,7 +3188,7 @@ _public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) {
         assert_return(s, -EINVAL);
         assert_return(pid, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *pid = s->child.pid;
         return 0;
@@ -2793,7 +3197,7 @@ _public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) {
 _public_ int sd_event_source_get_child_pidfd(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->child.pidfd < 0)
                 return -EOPNOTSUPP;
@@ -2804,7 +3208,7 @@ _public_ int sd_event_source_get_child_pidfd(sd_event_source *s) {
 _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo_t *si, unsigned flags) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
         assert_return(SIGNAL_VALID(sig), -EINVAL);
 
         /* If we already have seen indication the process exited refuse sending a signal early. This way we
@@ -2849,6 +3253,7 @@ _public_ int sd_event_source_send_child_signal(sd_event_source *s, int sig, cons
 _public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->child.pidfd < 0)
                 return -EOPNOTSUPP;
@@ -2859,6 +3264,7 @@ _public_ int sd_event_source_get_child_pidfd_own(sd_event_source *s) {
 _public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->child.pidfd < 0)
                 return -EOPNOTSUPP;
@@ -2870,6 +3276,7 @@ _public_ int sd_event_source_set_child_pidfd_own(sd_event_source *s, int own) {
 _public_ int sd_event_source_get_child_process_own(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->child.process_owned;
 }
@@ -2877,6 +3284,7 @@ _public_ int sd_event_source_get_child_process_own(sd_event_source *s) {
 _public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) {
         assert_return(s, -EINVAL);
         assert_return(s->type == SOURCE_CHILD, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         s->child.process_owned = own;
         return 0;
@@ -2886,7 +3294,7 @@ _public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask
         assert_return(s, -EINVAL);
         assert_return(mask, -EINVAL);
         assert_return(s->type == SOURCE_INOTIFY, -EDOM);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         *mask = s->inotify.mask;
         return 0;
@@ -2898,7 +3306,7 @@ _public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
         assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(s->event), -ECHILD);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->prepare == callback)
                 return 0;
@@ -2926,6 +3334,7 @@ _public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t
 
 _public_ void* sd_event_source_get_userdata(sd_event_source *s) {
         assert_return(s, NULL);
+        assert_return(!event_origin_changed(s->event), NULL);
 
         return s->userdata;
 }
@@ -2934,6 +3343,7 @@ _public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata)
         void *ret;
 
         assert_return(s, NULL);
+        assert_return(!event_origin_changed(s->event), NULL);
 
         ret = s->userdata;
         s->userdata = userdata;
@@ -3180,7 +3590,7 @@ static int event_arm_timer(
         assert_se(d->fd >= 0);
 
         if (t == 0) {
-                /* We don' want to disarm here, just mean some time looooong ago. */
+                /* We don't want to disarm here, just mean some time looooong ago. */
                 its.it_value.tv_sec = 0;
                 its.it_value.tv_nsec = 1;
         } else
@@ -3604,6 +4014,115 @@ static int process_inotify(sd_event *e) {
         return done;
 }
 
+static int process_memory_pressure(sd_event_source *s, uint32_t revents) {
+        assert(s);
+        assert(s->type == SOURCE_MEMORY_PRESSURE);
+
+        if (s->pending)
+                s->memory_pressure.revents |= revents;
+        else
+                s->memory_pressure.revents = revents;
+
+        return source_set_pending(s, true);
+}
+
+static int source_memory_pressure_write(sd_event_source *s) {
+        ssize_t n;
+        int r;
+
+        assert(s);
+        assert(s->type == SOURCE_MEMORY_PRESSURE);
+
+        /* once we start writing, the buffer is locked, we allow no further changes. */
+        s->memory_pressure.locked = true;
+
+        if (s->memory_pressure.write_buffer_size > 0) {
+                n = write(s->memory_pressure.fd, s->memory_pressure.write_buffer, s->memory_pressure.write_buffer_size);
+                if (n < 0) {
+                        if (!ERRNO_IS_TRANSIENT(errno)) {
+                                /* If kernel is built with CONFIG_PSI_DEFAULT_DISABLED it will expose PSI
+                                 * files, but then generates EOPNOSUPP on read() and write() (instead of on
+                                 * open()!). This sucks hard, since we can only detect this kind of failure
+                                 * so late. Let's make the best of it, and turn off the event source like we
+                                 * do for failed event source handlers. */
+
+                                log_debug_errno(errno, "Writing memory pressure settings to kernel failed, disabling memory pressure event source: %m");
+                                assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0);
+                                return 0;
+                        }
+
+                        n = 0;
+                }
+        } else
+                n = 0;
+
+        assert(n >= 0);
+
+        if ((size_t) n == s->memory_pressure.write_buffer_size) {
+                s->memory_pressure.write_buffer = mfree(s->memory_pressure.write_buffer);
+
+                if (n > 0) {
+                        s->memory_pressure.write_buffer_size = 0;
+
+                        /* Update epoll events mask, since we have now written everything and don't care for EPOLLOUT anymore */
+                        r = source_memory_pressure_register(s, s->enabled);
+                        if (r < 0)
+                                return r;
+                }
+        } else if (n > 0) {
+                _cleanup_free_ void *c = NULL;
+
+                assert((size_t) n < s->memory_pressure.write_buffer_size);
+
+                c = memdup((uint8_t*) s->memory_pressure.write_buffer + n, s->memory_pressure.write_buffer_size - n);
+                if (!c)
+                        return -ENOMEM;
+
+                free_and_replace(s->memory_pressure.write_buffer, c);
+                s->memory_pressure.write_buffer_size -= n;
+                return 1;
+        }
+
+        return 0;
+}
+
+static int source_memory_pressure_initiate_dispatch(sd_event_source *s) {
+        int r;
+
+        assert(s);
+        assert(s->type == SOURCE_MEMORY_PRESSURE);
+
+        r = source_memory_pressure_write(s);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return 1; /* if we wrote something, then don't continue with dispatching user dispatch
+                           * function. Instead, shortcut it so that we wait for next EPOLLOUT immediately. */
+
+        /* No pending incoming IO? Then let's not continue further */
+        if ((s->memory_pressure.revents & (EPOLLIN|EPOLLPRI)) == 0) {
+
+                /* Treat IO errors on the notifier the same ways errors returned from a callback */
+                if ((s->memory_pressure.revents & (EPOLLHUP|EPOLLERR|EPOLLRDHUP)) != 0)
+                        return -EIO;
+
+                return 1; /* leave dispatch, we already processed everything */
+        }
+
+        if (s->memory_pressure.revents & EPOLLIN) {
+                uint8_t pipe_buf[PIPE_BUF];
+                ssize_t n;
+
+                /* If the fd is readable, then flush out anything that might be queued */
+
+                n = read(s->memory_pressure.fd, pipe_buf, sizeof(pipe_buf));
+                if (n < 0 && !ERRNO_IS_TRANSIENT(errno))
+                        return -errno;
+        }
+
+        return 0; /* go on, dispatch to user callback */
+}
+
 static int source_dispatch(sd_event_source *s) {
         EventSourceType saved_type;
         sd_event *saved_event;
@@ -3652,6 +4171,16 @@ static int source_dispatch(sd_event_source *s) {
                 }
         }
 
+        if (s->type == SOURCE_MEMORY_PRESSURE) {
+                r = source_memory_pressure_initiate_dispatch(s);
+                if (r == -EIO) /* handle EIO errors similar to callback errors */
+                        goto finish;
+                if (r < 0)
+                        return r;
+                if (r > 0) /* already handled */
+                        return 1;
+        }
+
         if (s->enabled == SD_EVENT_ONESHOT) {
                 r = sd_event_source_set_enabled(s, SD_EVENT_OFF);
                 if (r < 0)
@@ -3738,6 +4267,10 @@ static int source_dispatch(sd_event_source *s) {
                 break;
         }
 
+        case SOURCE_MEMORY_PRESSURE:
+                r = s->memory_pressure.callback(s, s->userdata);
+                break;
+
         case SOURCE_WATCHDOG:
         case _SOURCE_EVENT_SOURCE_TYPE_MAX:
         case _SOURCE_EVENT_SOURCE_TYPE_INVALID:
@@ -3746,6 +4279,7 @@ static int source_dispatch(sd_event_source *s) {
 
         s->dispatching = false;
 
+finish:
         if (r < 0) {
                 log_debug_errno(r, "Event source %s (type %s) returned error, %s: %m",
                                 strna(s->description),
@@ -3777,12 +4311,9 @@ static int event_prepare(sd_event *e) {
                         break;
 
                 s->prepare_iteration = e->iteration;
-                r = prioq_reshuffle(e->prepare, s, &s->prepare_index);
-                if (r < 0)
-                        return r;
+                prioq_reshuffle(e->prepare, s, &s->prepare_index);
 
                 assert(s->prepare);
-
                 s->dispatching = true;
                 r = s->prepare(s, s->userdata);
                 s->dispatching = false;
@@ -3899,12 +4430,36 @@ static void event_close_inode_data_fds(sd_event *e) {
         }
 }
 
+static int event_memory_pressure_write_list(sd_event *e) {
+        int r;
+
+        assert(e);
+
+        for (;;) {
+                sd_event_source *s;
+
+                s = LIST_POP(memory_pressure.write_list, e->memory_pressure_write_list);
+                if (!s)
+                        break;
+
+                assert(s->type == SOURCE_MEMORY_PRESSURE);
+                assert(s->memory_pressure.write_buffer_size > 0);
+                s->memory_pressure.in_write_list = false;
+
+                r = source_memory_pressure_write(s);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 _public_ int sd_event_prepare(sd_event *e) {
         int r;
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
@@ -3927,6 +4482,10 @@ _public_ int sd_event_prepare(sd_event *e) {
         if (r < 0)
                 return r;
 
+        r = event_memory_pressure_write_list(e);
+        if (r < 0)
+                return r;
+
         r = event_arm_timer(e, &e->realtime);
         if (r < 0)
                 return r;
@@ -3972,15 +4531,18 @@ static int epoll_wait_usec(
                 usec_t timeout) {
 
         int msec;
-#if 0
+        /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not. */
+
+#if HAVE_EPOLL_PWAIT2
         static bool epoll_pwait2_absent = false;
         int r;
 
-        /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not.
-         *
-         * FIXME: this is temporarily disabled until epoll_pwait2() becomes more widely available.
-         * See https://github.com/systemd/systemd/pull/18973 and
-         * https://github.com/systemd/systemd/issues/19052. */
+        /* epoll_pwait2() was added to Linux 5.11 (2021-02-14) and to glibc in 2.35 (2022-02-03). In contrast
+         * to other syscalls we don't bother with our own fallback syscall wrappers on old libcs, since this
+         * is not that obvious to implement given the libc and kernel definitions differ in the last
+         * argument. Moreover, the only reason to use it is the more accurate time-outs (which is not a
+         * biggie), let's hence rely on glibc's definitions, and fallback to epoll_pwait() when that's
+         * missing. */
 
         if (!epoll_pwait2_absent && timeout != USEC_INFINITY) {
                 r = epoll_pwait2(fd,
@@ -4089,6 +4651,10 @@ static int process_epoll(sd_event *e, usec_t timeout, int64_t threshold, int64_t
                                         r = process_pidfd(e, s, e->event_queue[i].events);
                                         break;
 
+                                case SOURCE_MEMORY_PRESSURE:
+                                        r = process_memory_pressure(s, e->event_queue[i].events);
+                                        break;
+
                                 default:
                                         assert_not_reached();
                                 }
@@ -4132,7 +4698,7 @@ _public_ int sd_event_wait(sd_event *e, uint64_t timeout) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_ARMED, -EBUSY);
 
@@ -4235,7 +4801,7 @@ _public_ int sd_event_dispatch(sd_event *e) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_PENDING, -EBUSY);
 
@@ -4275,7 +4841,7 @@ _public_ int sd_event_run(sd_event *e, uint64_t timeout) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
@@ -4323,9 +4889,10 @@ _public_ int sd_event_loop(sd_event *e) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
         assert_return(e->state == SD_EVENT_INITIAL, -EBUSY);
 
+
         PROTECT_EVENT(e);
 
         while (e->state != SD_EVENT_FINISHED) {
@@ -4340,7 +4907,7 @@ _public_ int sd_event_loop(sd_event *e) {
 _public_ int sd_event_get_fd(sd_event *e) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         return e->epoll_fd;
 }
@@ -4348,7 +4915,7 @@ _public_ int sd_event_get_fd(sd_event *e) {
 _public_ int sd_event_get_state(sd_event *e) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         return e->state;
 }
@@ -4357,7 +4924,7 @@ _public_ int sd_event_get_exit_code(sd_event *e, int *code) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(code, -EINVAL);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!e->exit_requested)
                 return -ENODATA;
@@ -4370,7 +4937,7 @@ _public_ int sd_event_exit(sd_event *e, int code) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(e->state != SD_EVENT_FINISHED, -ESTALE);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         e->exit_requested = true;
         e->exit_code = code;
@@ -4382,7 +4949,7 @@ _public_ int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(usec, -EINVAL);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock))
                 return -EOPNOTSUPP;
@@ -4426,7 +4993,7 @@ _public_ int sd_event_get_tid(sd_event *e, pid_t *tid) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
         assert_return(tid, -EINVAL);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (e->tid != 0) {
                 *tid = e->tid;
@@ -4441,7 +5008,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) {
 
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         if (e->watchdog == !!b)
                 return e->watchdog;
@@ -4491,7 +5058,7 @@ fail:
 _public_ int sd_event_get_watchdog(sd_event *e) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         return e->watchdog;
 }
@@ -4499,7 +5066,7 @@ _public_ int sd_event_get_watchdog(sd_event *e) {
 _public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
         assert_return(e, -EINVAL);
         assert_return(e = event_resolve(e), -ENOPKG);
-        assert_return(!event_pid_changed(e), -ECHILD);
+        assert_return(!event_origin_changed(e), -ECHILD);
 
         *ret = e->iteration;
         return 0;
@@ -4507,6 +5074,8 @@ _public_ int sd_event_get_iteration(sd_event *e, uint64_t *ret) {
 
 _public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback) {
         assert_return(s, -EINVAL);
+        assert_return(s->event, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         s->destroy_callback = callback;
         return 0;
@@ -4514,6 +5083,7 @@ _public_ int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_d
 
 _public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (ret)
                 *ret = s->destroy_callback;
@@ -4523,6 +5093,7 @@ _public_ int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_d
 
 _public_ int sd_event_source_get_floating(sd_event_source *s) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->floating;
 }
@@ -4530,6 +5101,7 @@ _public_ int sd_event_source_get_floating(sd_event_source *s) {
 
 _public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->floating == !!b)
                 return 0;
@@ -4554,6 +5126,7 @@ _public_ int sd_event_source_set_floating(sd_event_source *s, int b) {
 _public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) {
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         return s->exit_on_failure;
 }
@@ -4561,6 +5134,7 @@ _public_ int sd_event_source_get_exit_on_failure(sd_event_source *s) {
 _public_ int sd_event_source_set_exit_on_failure(sd_event_source *s, int b) {
         assert_return(s, -EINVAL);
         assert_return(s->type != SOURCE_EXIT, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (s->exit_on_failure == !!b)
                 return 0;
@@ -4573,6 +5147,7 @@ _public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval
         int r;
 
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         /* Turning on ratelimiting on event source types that don't support it, is a loggable offense. Doing
          * so is a programming error. */
@@ -4590,6 +5165,7 @@ _public_ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval
 
 _public_ int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, sd_event_handler_t callback) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         s->ratelimit_expire_callback = callback;
         return 0;
@@ -4597,6 +5173,7 @@ _public_ int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, s
 
 _public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval, unsigned *ret_burst) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         /* Querying whether an event source has ratelimiting configured is not a loggable offense, hence
          * don't use assert_return(). Unlike turning on ratelimiting it's not really a programming error. */
@@ -4616,6 +5193,7 @@ _public_ int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_int
 
 _public_ int sd_event_source_is_ratelimited(sd_event_source *s) {
         assert_return(s, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
 
         if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type))
                 return false;
@@ -4626,6 +5204,27 @@ _public_ int sd_event_source_is_ratelimited(sd_event_source *s) {
         return s->ratelimited;
 }
 
+_public_ int sd_event_source_leave_ratelimit(sd_event_source *s) {
+        int r;
+
+        assert_return(s, -EINVAL);
+
+        if (!EVENT_SOURCE_CAN_RATE_LIMIT(s->type))
+                return 0;
+
+        if (!ratelimit_configured(&s->rate_limit))
+                return 0;
+
+        if (!s->ratelimited)
+                return 0;
+
+        r = event_source_leave_ratelimit(s, /* run_callback */ false);
+        if (r < 0)
+                return r;
+
+        return 1; /* tell caller that we indeed just left the ratelimit state */
+}
+
 _public_ int sd_event_set_signal_exit(sd_event *e, int b) {
         bool change = false;
         int r;
@@ -4677,4 +5276,94 @@ _public_ int sd_event_set_signal_exit(sd_event *e, int b) {
 
         return change;
 }
+
+_public_ int sd_event_source_set_memory_pressure_type(sd_event_source *s, const char *ty) {
+        _cleanup_free_ char *b = NULL;
+        _cleanup_free_ void *w = NULL;
+
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM);
+        assert_return(ty, -EINVAL);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
+
+        if (!STR_IN_SET(ty, "some", "full"))
+                return -EINVAL;
+
+        if (s->memory_pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */
+                return -EBUSY;
+
+        char* space = memchr(s->memory_pressure.write_buffer, ' ', s->memory_pressure.write_buffer_size);
+        if (!space)
+                return -EINVAL;
+
+        size_t l = (char*) space - (char*) s->memory_pressure.write_buffer;
+        b = memdup_suffix0(s->memory_pressure.write_buffer, l);
+        if (!b)
+                return -ENOMEM;
+        if (!STR_IN_SET(b, "some", "full"))
+                return -EINVAL;
+
+        if (streq(b, ty))
+                return 0;
+
+        size_t nl = strlen(ty) + (s->memory_pressure.write_buffer_size - l);
+        w = new(char, nl);
+        if (!w)
+                return -ENOMEM;
+
+        memcpy(stpcpy(w, ty), space, (s->memory_pressure.write_buffer_size - l));
+
+        free_and_replace(s->memory_pressure.write_buffer, w);
+        s->memory_pressure.write_buffer_size = nl;
+        s->memory_pressure.locked = false;
+
+        return 1;
+}
+
+_public_ int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec) {
+        _cleanup_free_ char *b = NULL;
+        _cleanup_free_ void *w = NULL;
+
+        assert_return(s, -EINVAL);
+        assert_return(s->type == SOURCE_MEMORY_PRESSURE, -EDOM);
+        assert_return(!event_origin_changed(s->event), -ECHILD);
+
+        if (threshold_usec <= 0 || threshold_usec >= UINT64_MAX)
+                return -ERANGE;
+        if (window_usec <= 0 || window_usec >= UINT64_MAX)
+                return -ERANGE;
+        if (threshold_usec > window_usec)
+                return -EINVAL;
+
+        if (s->memory_pressure.locked) /* Refuse adjusting parameters, if caller told us how to watch for events */
+                return -EBUSY;
+
+        char* space = memchr(s->memory_pressure.write_buffer, ' ', s->memory_pressure.write_buffer_size);
+        if (!space)
+                return -EINVAL;
+
+        size_t l = (char*) space - (char*) s->memory_pressure.write_buffer;
+        b = memdup_suffix0(s->memory_pressure.write_buffer, l);
+        if (!b)
+                return -ENOMEM;
+        if (!STR_IN_SET(b, "some", "full"))
+                return -EINVAL;
+
+        if (asprintf((char**) &w,
+                     "%s " USEC_FMT " " USEC_FMT "",
+                     b,
+                     threshold_usec,
+                     window_usec) < 0)
+                return -EINVAL;
+
+        l = strlen(w) + 1;
+        if (memcmp_nn(s->memory_pressure.write_buffer, s->memory_pressure.write_buffer_size, w, l) == 0)
+                return 0;
+
+        free_and_replace(s->memory_pressure.write_buffer, w);
+        s->memory_pressure.write_buffer_size = l;
+        s->memory_pressure.locked = false;
+
+        return 1;
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c
index 66247fd1..c20f7325 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c
+++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.c
@@ -7,6 +7,7 @@
 #include <unistd.h>
 
 #include "fd-util.h"
+#include "fs-util.h"
 #include "hexdecoct.h"
 #include "id128-util.h"
 #include "io-util.h"
@@ -16,60 +17,51 @@
 
 #if 0 /* NM_IGNORED */
 bool id128_is_valid(const char *s) {
-        size_t i, l;
+        size_t l;
 
         assert(s);
 
         l = strlen(s);
-        if (l == 32) {
 
+        if (l == SD_ID128_STRING_MAX - 1)
                 /* Plain formatted 128bit hex string */
+                return in_charset(s, HEXDIGITS);
 
-                for (i = 0; i < l; i++) {
-                        char c = s[i];
-
-                        if (!ascii_isdigit(c) &&
-                            !(c >= 'a' && c <= 'f') &&
-                            !(c >= 'A' && c <= 'F'))
-                                return false;
-                }
-
-        } else if (l == 36) {
-
+        if (l == SD_ID128_UUID_STRING_MAX - 1) {
                 /* Formatted UUID */
-
-                for (i = 0; i < l; i++) {
+                for (size_t i = 0; i < l; i++) {
                         char c = s[i];
 
                         if (IN_SET(i, 8, 13, 18, 23)) {
                                 if (c != '-')
                                         return false;
-                        } else {
-                                if (!ascii_isdigit(c) &&
-                                    !(c >= 'a' && c <= 'f') &&
-                                    !(c >= 'A' && c <= 'F'))
-                                        return false;
-                        }
+                        } else if (!ascii_ishex(c))
+                                return false;
                 }
+                return true;
+        }
 
-        } else
-                return false;
-
-        return true;
+        return false;
 }
 #endif /* NM_IGNORED */
 
-int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) {
-        char buffer[36 + 2];
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret) {
+        char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
+        sd_id128_t id;
         ssize_t l;
+        int r;
 
         assert(fd >= 0);
-        assert(f < _ID128_FORMAT_MAX);
 
         /* Reads an 128bit ID from a file, which may either be in plain format (32 hex digits), or in UUID format, both
          * optionally followed by a newline and nothing else. ID files should really be newline terminated, but if they
          * aren't that's OK too, following the rule of "Be conservative in what you send, be liberal in what you
-         * accept". */
+         * accept".
+         *
+         * This returns the following:
+         *     -ENOMEDIUM: an empty string,
+         *     -ENOPKG:    "uninitialized" or "uninitialized\n",
+         *     -EUCLEAN:   other invalid strings. */
 
         l = loop_read(fd, buffer, sizeof(buffer), false); /* we expect a short read of either 32/33 or 36/37 chars */
         if (l < 0)
@@ -79,76 +71,91 @@ int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret) {
 
         switch (l) {
 
-        case 13:
-        case 14:
-                /* Treat an "uninitialized" id file like an empty one */
-                return f == ID128_PLAIN_OR_UNINIT && strneq(buffer, "uninitialized\n", l) ? -ENOMEDIUM : -EINVAL;
+        case STRLEN("uninitialized"):
+        case STRLEN("uninitialized\n"):
+                return strneq(buffer, "uninitialized\n", l) ? -ENOPKG : -EINVAL;
 
-        case 33: /* plain UUID with trailing newline */
-                if (buffer[32] != '\n')
-                        return -EINVAL;
+        case SD_ID128_STRING_MAX: /* plain UUID with trailing newline */
+                if (buffer[SD_ID128_STRING_MAX-1] != '\n')
+                        return -EUCLEAN;
 
                 _fallthrough_;
-        case 32: /* plain UUID without trailing newline */
-                if (f == ID128_UUID)
-                        return -EINVAL;
+        case SD_ID128_STRING_MAX-1: /* plain UUID without trailing newline */
+                if (!FLAGS_SET(f, ID128_FORMAT_PLAIN))
+                        return -EUCLEAN;
 
-                buffer[32] = 0;
+                buffer[SD_ID128_STRING_MAX-1] = 0;
                 break;
 
-        case 37: /* RFC UUID with trailing newline */
-                if (buffer[36] != '\n')
-                        return -EINVAL;
+        case SD_ID128_UUID_STRING_MAX: /* RFC UUID with trailing newline */
+                if (buffer[SD_ID128_UUID_STRING_MAX-1] != '\n')
+                        return -EUCLEAN;
 
                 _fallthrough_;
-        case 36: /* RFC UUID without trailing newline */
-                if (IN_SET(f, ID128_PLAIN, ID128_PLAIN_OR_UNINIT))
-                        return -EINVAL;
+        case SD_ID128_UUID_STRING_MAX-1: /* RFC UUID without trailing newline */
+                if (!FLAGS_SET(f, ID128_FORMAT_UUID))
+                        return -EUCLEAN;
 
-                buffer[36] = 0;
+                buffer[SD_ID128_UUID_STRING_MAX-1] = 0;
                 break;
 
         default:
-                return -EINVAL;
+                return -EUCLEAN;
         }
 
-        return sd_id128_from_string(buffer, ret);
+        r = sd_id128_from_string(buffer, &id);
+        if (r == -EINVAL)
+                return -EUCLEAN;
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+                return -ENOMEDIUM;
+
+        if (ret)
+                *ret = id;
+        return 0;
 }
 
-int id128_read(const char *p, Id128Format f, sd_id128_t *ret) {
-        _cleanup_close_ int fd = -1;
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret) {
+        _cleanup_close_ int fd = -EBADF;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
 
-        fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+        fd = xopenat(dir_fd, path, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0);
         if (fd < 0)
-                return -errno;
+                return fd;
 
         return id128_read_fd(fd, f, ret);
 }
 
 #if 0 /* NM_IGNORED */
-int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) {
-        char buffer[36 + 2];
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id) {
+        char buffer[SD_ID128_UUID_STRING_MAX + 1]; /* +1 is for trailing newline */
         size_t sz;
         int r;
 
         assert(fd >= 0);
-        assert(f < _ID128_FORMAT_MAX);
+        assert(IN_SET((f & ID128_FORMAT_ANY), ID128_FORMAT_PLAIN, ID128_FORMAT_UUID));
 
-        if (f != ID128_UUID) {
+        if (FLAGS_SET(f, ID128_REFUSE_NULL) && sd_id128_is_null(id))
+                return -ENOMEDIUM;
+
+        if (FLAGS_SET(f, ID128_FORMAT_PLAIN)) {
                 assert_se(sd_id128_to_string(id, buffer));
-                buffer[SD_ID128_STRING_MAX - 1] = '\n';
                 sz = SD_ID128_STRING_MAX;
         } else {
                 assert_se(sd_id128_to_uuid_string(id, buffer));
-                buffer[SD_ID128_UUID_STRING_MAX - 1] = '\n';
                 sz = SD_ID128_UUID_STRING_MAX;
         }
 
+        buffer[sz - 1] = '\n';
         r = loop_write(fd, buffer, sz, false);
         if (r < 0)
                 return r;
 
-        if (do_sync) {
+        if (FLAGS_SET(f, ID128_SYNC_ON_WRITE)) {
                 r = fsync_full(fd);
                 if (r < 0)
                         return r;
@@ -157,14 +164,17 @@ int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync) {
         return 0;
 }
 
-int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync) {
-        _cleanup_close_ int fd = -1;
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) {
+        _cleanup_close_ int fd = -EBADF;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
 
-        fd = open(p, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, 0444);
+        fd = xopenat(dir_fd, path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_TRUNC, /* xopen_flags = */ 0, 0444);
         if (fd < 0)
-                return -errno;
+                return fd;
 
-        return id128_write_fd(fd, f, id, do_sync);
+        return id128_write_fd(fd, f, id);
 }
 
 void id128_hash_func(const sd_id128_t *p, struct siphash *state) {
@@ -189,6 +199,7 @@ sd_id128_t id128_make_v4_uuid(sd_id128_t id) {
 }
 
 DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func);
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(id128_hash_ops_free, sd_id128_t, id128_hash_func, id128_compare_func, free);
 
 int id128_get_product(sd_id128_t *ret) {
         sd_id128_t uuid;
@@ -199,9 +210,9 @@ int id128_get_product(sd_id128_t *ret) {
         /* Reads the systems product UUID from DMI or devicetree (where it is located on POWER). This is
          * particularly relevant in VM environments, where VM managers typically place a VM uuid there. */
 
-        r = id128_read("/sys/class/dmi/id/product_uuid", ID128_UUID, &uuid);
+        r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid);
         if (r == -ENOENT)
-                r = id128_read("/proc/device-tree/vm,uuid", ID128_UUID, &uuid);
+                r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid);
         if (r < 0)
                 return r;
 
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h
index 17b180c1..7bcbd8e5 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h
+++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/id128-util.h
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
 #include <stdbool.h>
 
 #include "sd-id128.h"
@@ -10,27 +11,42 @@
 
 bool id128_is_valid(const char *s) _pure_;
 
-typedef enum Id128Format {
-        ID128_ANY,
-        ID128_PLAIN,  /* formatted as 32 hex chars as-is */
-        ID128_PLAIN_OR_UNINIT,  /* formatted as 32 hex chars as-is; allow special "uninitialized"
-                                 * value when reading from file (id128_read() and id128_read_fd()).
-                                 *
-                                 * This format should be used when reading a machine-id file. */
-        ID128_UUID,   /* formatted as 36 character uuid string */
-        _ID128_FORMAT_MAX,
-} Id128Format;
+typedef enum Id128Flag {
+        ID128_FORMAT_PLAIN  = 1 << 0,  /* formatted as 32 hex chars as-is */
+        ID128_FORMAT_UUID   = 1 << 1,  /* formatted as 36 character uuid string */
+        ID128_FORMAT_ANY    = ID128_FORMAT_PLAIN | ID128_FORMAT_UUID,
 
-int id128_read_fd(int fd, Id128Format f, sd_id128_t *ret);
-int id128_read(const char *p, Id128Format f, sd_id128_t *ret);
+        ID128_SYNC_ON_WRITE = 1 << 2, /* Sync the file after write. Used only when writing an ID. */
+        ID128_REFUSE_NULL   = 1 << 3, /* Refuse all zero ID with -ENOMEDIUM. */
+} Id128Flag;
 
-int id128_write_fd(int fd, Id128Format f, sd_id128_t id, bool do_sync);
-int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync);
+int id128_read_fd(int fd, Id128Flag f, sd_id128_t *ret);
+int id128_read_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t *ret);
+static inline int id128_read(const char *path, Id128Flag f, sd_id128_t *ret) {
+        return id128_read_at(AT_FDCWD, path, f, ret);
+}
+
+int id128_write_fd(int fd, Id128Flag f, sd_id128_t id);
+int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id);
+static inline int id128_write(const char *path, Id128Flag f, sd_id128_t id) {
+        return id128_write_at(AT_FDCWD, path, f, id);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret);
+int id128_get_machine_at(int rfd, sd_id128_t *ret);
 
 void id128_hash_func(const sd_id128_t *p, struct siphash *state);
 int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
 extern const struct hash_ops id128_hash_ops;
+extern const struct hash_ops id128_hash_ops_free;
 
 sd_id128_t id128_make_v4_uuid(sd_id128_t id);
 
 int id128_get_product(sd_id128_t *ret);
+
+/* A helper to check for the three relevant cases of "machine ID not initialized" */
+#define ERRNO_IS_MACHINE_ID_UNSET(r)            \
+        IN_SET(abs(r),                          \
+               ENOENT,                          \
+               ENOMEDIUM,                       \
+               ENOPKG)
diff --git a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c
index b7d7788c..c63e1a97 100644
--- a/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c
+++ b/src/libnm-systemd-core/src/libsystemd/sd-id128/sd-id128.c
@@ -9,6 +9,7 @@
 #include "sd-id128.h"
 
 #include "alloc-util.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "hexdecoct.h"
 #include "hmac.h"
@@ -16,20 +17,25 @@
 #include "io-util.h"
 #include "macro.h"
 #include "missing_syscall.h"
+#include "missing_threads.h"
+#include "path-util.h"
 #include "random-util.h"
+#include "stat-util.h"
 #include "user-util.h"
-#include "util.h"
 
 #if 0 /* NM_IGNORED */
 _public_ char *sd_id128_to_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD_ID128_STRING_MAX]) {
+        size_t k = 0;
+
         assert_return(s, NULL);
 
-        for (size_t n = 0; n < 16; n++) {
-                s[n*2] = hexchar(id.bytes[n] >> 4);
-                s[n*2+1] = hexchar(id.bytes[n] & 0xF);
+        for (size_t n = 0; n < sizeof(sd_id128_t); n++) {
+                s[k++] = hexchar(id.bytes[n] >> 4);
+                s[k++] = hexchar(id.bytes[n] & 0xF);
         }
 
-        s[SD_ID128_STRING_MAX-1] = 0;
+        assert(k == SD_ID128_STRING_MAX - 1);
+        s[k] = 0;
 
         return s;
 }
@@ -41,7 +47,7 @@ _public_ char *sd_id128_to_uuid_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD
 
         /* Similar to sd_id128_to_string() but formats the result as UUID instead of plain hex chars */
 
-        for (size_t n = 0; n < 16; n++) {
+        for (size_t n = 0; n < sizeof(sd_id128_t); n++) {
 
                 if (IN_SET(n, 4, 6, 8, 10))
                         s[k++] = '-';
@@ -57,14 +63,14 @@ _public_ char *sd_id128_to_uuid_string(sd_id128_t id, char s[_SD_ARRAY_STATIC SD
 }
 #endif /* NM_IGNORED */
 
-_public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) {
-        unsigned n, i;
+_public_ int sd_id128_from_string(const char *s, sd_id128_t *ret) {
+        size_t n, i;
         sd_id128_t t;
         bool is_guid = false;
 
         assert_return(s, -EINVAL);
 
-        for (n = 0, i = 0; n < 16;) {
+        for (n = 0, i = 0; n < sizeof(sd_id128_t);) {
                 int a, b;
 
                 if (s[i] == '-') {
@@ -94,7 +100,7 @@ _public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) {
                 t.bytes[n++] = (a << 4) | b;
         }
 
-        if (i != (is_guid ? 36 : 32))
+        if (i != (is_guid ? SD_ID128_UUID_STRING_MAX : SD_ID128_STRING_MAX) - 1)
                 return -EINVAL;
 
         if (s[i] != 0)
@@ -125,34 +131,65 @@ _public_ int sd_id128_get_machine(sd_id128_t *ret) {
         static thread_local sd_id128_t saved_machine_id = {};
         int r;
 
-        assert_return(ret, -EINVAL);
-
         if (sd_id128_is_null(saved_machine_id)) {
-                r = id128_read("/etc/machine-id", ID128_PLAIN, &saved_machine_id);
+                r = id128_read("/etc/machine-id", ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, &saved_machine_id);
                 if (r < 0)
                         return r;
-
-                if (sd_id128_is_null(saved_machine_id))
-                        return -ENOMEDIUM;
         }
 
-        *ret = saved_machine_id;
+        if (ret)
+                *ret = saved_machine_id;
         return 0;
 }
 
+#if 0 /* NM_IGNORED */
+int id128_get_machine_at(int rfd, sd_id128_t *ret) {
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(rfd >= 0 || rfd == AT_FDCWD);
+
+        r = dir_fd_is_root_or_cwd(rfd);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return sd_id128_get_machine(ret);
+
+        fd = chase_and_openat(rfd, "/etc/machine-id", CHASE_AT_RESOLVE_IN_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+        if (fd < 0)
+                return fd;
+
+        return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+
+int id128_get_machine(const char *root, sd_id128_t *ret) {
+        _cleanup_close_ int fd = -EBADF;
+
+        if (empty_or_root(root))
+                return sd_id128_get_machine(ret);
+
+        fd = chase_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
+        if (fd < 0)
+                return fd;
+
+        return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret);
+}
+#endif /* NM_IGNORED */
+
 _public_ int sd_id128_get_boot(sd_id128_t *ret) {
         static thread_local sd_id128_t saved_boot_id = {};
         int r;
 
-        assert_return(ret, -EINVAL);
-
         if (sd_id128_is_null(saved_boot_id)) {
-                r = id128_read("/proc/sys/kernel/random/boot_id", ID128_UUID, &saved_boot_id);
+                r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, &saved_boot_id);
+                if (r == -ENOENT && proc_mounted() == 0)
+                        return -ENOSYS;
                 if (r < 0)
                         return r;
         }
 
-        *ret = saved_boot_id;
+        if (ret)
+                *ret = saved_boot_id;
         return 0;
 }
 
@@ -203,22 +240,22 @@ static int get_invocation_from_keyring(sd_id128_t *ret) {
         /* Chop off the final description string */
         d = strrchr(description, ';');
         if (!d)
-                return -EIO;
+                return -EUCLEAN;
         *d = 0;
 
         /* Look for the permissions */
         p = strrchr(description, ';');
         if (!p)
-                return -EIO;
+                return -EUCLEAN;
 
         errno = 0;
         perms = strtoul(p + 1, &e, 16);
         if (errno > 0)
                 return -errno;
         if (e == p + 1) /* Read at least one character */
-                return -EIO;
+                return -EUCLEAN;
         if (e != d) /* Must reached the end */
-                return -EIO;
+                return -EUCLEAN;
 
         if ((perms & ~MAX_PERMS) != 0)
                 return -EPERM;
@@ -228,7 +265,7 @@ static int get_invocation_from_keyring(sd_id128_t *ret) {
         /* Look for the group ID */
         g = strrchr(description, ';');
         if (!g)
-                return -EIO;
+                return -EUCLEAN;
         r = parse_gid(g + 1, &gid);
         if (r < 0)
                 return r;
@@ -239,7 +276,7 @@ static int get_invocation_from_keyring(sd_id128_t *ret) {
         /* Look for the user ID */
         u = strrchr(description, ';');
         if (!u)
-                return -EIO;
+                return -EUCLEAN;
         r = parse_uid(u + 1, &uid);
         if (r < 0)
                 return r;
@@ -250,13 +287,14 @@ static int get_invocation_from_keyring(sd_id128_t *ret) {
         if (c < 0)
                 return -errno;
         if (c != sizeof(sd_id128_t))
-                return -EIO;
+                return -EUCLEAN;
 
         return 0;
 }
 
 static int get_invocation_from_environment(sd_id128_t *ret) {
         const char *e;
+        int r;
 
         assert(ret);
 
@@ -264,33 +302,31 @@ static int get_invocation_from_environment(sd_id128_t *ret) {
         if (!e)
                 return -ENXIO;
 
-        return sd_id128_from_string(e, ret);
+        r = sd_id128_from_string(e, ret);
+        return r == -EINVAL ? -EUCLEAN : r;
 }
 
 _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
         static thread_local sd_id128_t saved_invocation_id = {};
         int r;
 
-        assert_return(ret, -EINVAL);
-
         if (sd_id128_is_null(saved_invocation_id)) {
                 /* We first check the environment. The environment variable is primarily relevant for user
                  * services, and sufficiently safe as long as no privilege boundary is involved. */
                 r = get_invocation_from_environment(&saved_invocation_id);
-                if (r >= 0) {
-                        *ret = saved_invocation_id;
-                        return 0;
-                } else if (r != -ENXIO)
-                        return r;
-
-                /* The kernel keyring is relevant for system services (as for user services we don't store
-                 * the invocation ID in the keyring, as there'd be no trust benefit in that). */
-                r = get_invocation_from_keyring(&saved_invocation_id);
+                if (r == -ENXIO)
+                        /* The kernel keyring is relevant for system services (as for user services we don't
+                         * store the invocation ID in the keyring, as there'd be no trust benefit in that). */
+                        r = get_invocation_from_keyring(&saved_invocation_id);
                 if (r < 0)
                         return r;
+
+                if (sd_id128_is_null(saved_invocation_id))
+                        return -ENOMEDIUM;
         }
 
-        *ret = saved_invocation_id;
+        if (ret)
+                *ret = saved_invocation_id;
         return 0;
 }
 
diff --git a/src/libnm-systemd-core/src/systemd/sd-device.h b/src/libnm-systemd-core/src/systemd/sd-device.h
new file mode 100644
index 00000000..e3d647f7
--- /dev/null
+++ b/src/libnm-systemd-core/src/systemd/sd-device.h
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#ifndef foosddevicehfoo
+#define foosddevicehfoo
+
+/***
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <https://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+
+#include "sd-event.h"
+#include "sd-id128.h"
+
+#include "_sd-common.h"
+
+_SD_BEGIN_DECLARATIONS;
+
+typedef struct sd_device sd_device;
+typedef struct sd_device_enumerator sd_device_enumerator;
+typedef struct sd_device_monitor sd_device_monitor;
+
+__extension__ typedef enum sd_device_action_t {
+        SD_DEVICE_ADD,
+        SD_DEVICE_REMOVE,
+        SD_DEVICE_CHANGE,
+        SD_DEVICE_MOVE,
+        SD_DEVICE_ONLINE,
+        SD_DEVICE_OFFLINE,
+        SD_DEVICE_BIND,
+        SD_DEVICE_UNBIND,
+        _SD_DEVICE_ACTION_MAX,
+        _SD_DEVICE_ACTION_INVALID = -EINVAL,
+        _SD_ENUM_FORCE_S64(DEVICE_ACTION)
+} sd_device_action_t;
+
+/* callback */
+
+typedef int (*sd_device_monitor_handler_t)(sd_device_monitor *m, sd_device *device, void *userdata);
+
+/* device */
+
+sd_device *sd_device_ref(sd_device *device);
+sd_device *sd_device_unref(sd_device *device);
+
+int sd_device_new_from_syspath(sd_device **ret, const char *syspath);
+int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum);
+int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname);
+int sd_device_new_from_device_id(sd_device **ret, const char *id);
+int sd_device_new_from_stat_rdev(sd_device **ret, const struct stat *st);
+int sd_device_new_from_devname(sd_device **ret, const char *devname);
+int sd_device_new_from_path(sd_device **ret, const char *path);
+int sd_device_new_from_ifname(sd_device **ret, const char *ifname);
+int sd_device_new_from_ifindex(sd_device **ret, int ifindex);
+
+int sd_device_new_child(sd_device **ret, sd_device *device, const char *suffix);
+
+int sd_device_get_parent(sd_device *child, sd_device **ret);
+int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret);
+
+int sd_device_get_syspath(sd_device *device, const char **ret);
+int sd_device_get_subsystem(sd_device *device, const char **ret);
+int sd_device_get_devtype(sd_device *device, const char **ret);
+int sd_device_get_devnum(sd_device *device, dev_t *devnum);
+int sd_device_get_ifindex(sd_device *device, int *ifindex);
+int sd_device_get_driver(sd_device *device, const char **ret);
+int sd_device_get_devpath(sd_device *device, const char **ret);
+int sd_device_get_devname(sd_device *device, const char **ret);
+int sd_device_get_sysname(sd_device *device, const char **ret);
+int sd_device_get_sysnum(sd_device *device, const char **ret);
+int sd_device_get_action(sd_device *device, sd_device_action_t *ret);
+int sd_device_get_seqnum(sd_device *device, uint64_t *ret);
+int sd_device_get_diskseq(sd_device *device, uint64_t *ret);
+
+int sd_device_get_is_initialized(sd_device *device);
+int sd_device_get_usec_initialized(sd_device *device, uint64_t *ret);
+int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *ret);
+
+const char *sd_device_get_tag_first(sd_device *device);
+const char *sd_device_get_tag_next(sd_device *device);
+const char *sd_device_get_current_tag_first(sd_device *device);
+const char *sd_device_get_current_tag_next(sd_device *device);
+const char *sd_device_get_devlink_first(sd_device *device);
+const char *sd_device_get_devlink_next(sd_device *device);
+const char *sd_device_get_property_first(sd_device *device, const char **value);
+const char *sd_device_get_property_next(sd_device *device, const char **value);
+const char *sd_device_get_sysattr_first(sd_device *device);
+const char *sd_device_get_sysattr_next(sd_device *device);
+sd_device *sd_device_get_child_first(sd_device *device, const char **ret_suffix);
+sd_device *sd_device_get_child_next(sd_device *device, const char **ret_suffix);
+
+int sd_device_has_tag(sd_device *device, const char *tag);
+int sd_device_has_current_tag(sd_device *device, const char *tag);
+int sd_device_get_property_value(sd_device *device, const char *key, const char **value);
+int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret);
+int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value);
+
+int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *value);
+int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr, const char *format, ...) _sd_printf_(3, 4);
+int sd_device_trigger(sd_device *device, sd_device_action_t action);
+int sd_device_trigger_with_uuid(sd_device *device, sd_device_action_t action, sd_id128_t *ret_uuid);
+int sd_device_open(sd_device *device, int flags);
+
+/* device enumerator */
+
+int sd_device_enumerator_new(sd_device_enumerator **ret);
+sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator);
+sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator);
+
+sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator);
+sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator);
+sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator);
+sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator);
+
+int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match);
+int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *sysattr, const char *value, int match);
+int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *property, const char *value);
+int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname);
+int sd_device_enumerator_add_nomatch_sysname(sd_device_enumerator *enumerator, const char *sysname);
+int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag);
+int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent);
+int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator);
+
+/* device monitor */
+
+int sd_device_monitor_new(sd_device_monitor **ret);
+sd_device_monitor *sd_device_monitor_ref(sd_device_monitor *m);
+sd_device_monitor *sd_device_monitor_unref(sd_device_monitor *m);
+
+int sd_device_monitor_set_receive_buffer_size(sd_device_monitor *m, size_t size);
+int sd_device_monitor_attach_event(sd_device_monitor *m, sd_event *event);
+int sd_device_monitor_detach_event(sd_device_monitor *m);
+sd_event *sd_device_monitor_get_event(sd_device_monitor *m);
+sd_event_source *sd_device_monitor_get_event_source(sd_device_monitor *m);
+int sd_device_monitor_set_description(sd_device_monitor *m, const char *description);
+int sd_device_monitor_get_description(sd_device_monitor *m, const char **ret);
+int sd_device_monitor_start(sd_device_monitor *m, sd_device_monitor_handler_t callback, void *userdata);
+int sd_device_monitor_stop(sd_device_monitor *m);
+
+int sd_device_monitor_filter_add_match_subsystem_devtype(sd_device_monitor *m, const char *subsystem, const char *devtype);
+int sd_device_monitor_filter_add_match_tag(sd_device_monitor *m, const char *tag);
+int sd_device_monitor_filter_add_match_sysattr(sd_device_monitor *m, const char *sysattr, const char *value, int match);
+int sd_device_monitor_filter_add_match_parent(sd_device_monitor *m, sd_device *device, int match);
+int sd_device_monitor_filter_update(sd_device_monitor *m);
+int sd_device_monitor_filter_remove(sd_device_monitor *m);
+
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device, sd_device_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device_enumerator, sd_device_enumerator_unref);
+_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device_monitor, sd_device_monitor_unref);
+
+_SD_END_DECLARATIONS;
+
+#endif
diff --git a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
index 2c66c51b..a9fa7856 100644
--- a/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
+++ b/src/libnm-systemd-core/src/systemd/sd-dhcp6-client.h
@@ -23,6 +23,7 @@
 #include <net/ethernet.h>
 #include <sys/types.h>
 
+#include "sd-device.h"
 #include "sd-dhcp6-lease.h"
 #include "sd-dhcp6-option.h"
 #include "sd-event.h"
@@ -263,6 +264,7 @@ int sd_dhcp6_client_set_address_request(sd_dhcp6_client *client,
 int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client,
                                       sd_dhcp6_option *v);
 int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable);
+int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable);
 
 int sd_dhcp6_client_get_lease(
                 sd_dhcp6_client *client,
@@ -279,6 +281,7 @@ int sd_dhcp6_client_attach_event(
                 int64_t priority);
 int sd_dhcp6_client_detach_event(sd_dhcp6_client *client);
 sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client);
+int sd_dhcp6_client_attach_device(sd_dhcp6_client *client, sd_device *dev);
 sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client);
 sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client);
 int sd_dhcp6_client_new(sd_dhcp6_client **ret);
diff --git a/src/libnm-systemd-core/src/systemd/sd-event.h b/src/libnm-systemd-core/src/systemd/sd-event.h
index cae4c867..49d69759 100644
--- a/src/libnm-systemd-core/src/systemd/sd-event.h
+++ b/src/libnm-systemd-core/src/systemd/sd-event.h
@@ -99,6 +99,7 @@ int sd_event_add_inotify_fd(sd_event *e, sd_event_source **s, int fd, uint32_t m
 int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
 int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
 int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
+int sd_event_add_memory_pressure(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata);
 
 int sd_event_prepare(sd_event *e);
 int sd_event_wait(sd_event *e, uint64_t usec);
@@ -160,6 +161,8 @@ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo
 int sd_event_source_send_child_signal(sd_event_source *s, int sig, const void *si, unsigned flags);
 #endif
 int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret);
+int sd_event_source_set_memory_pressure_type(sd_event_source *e, const char *ty);
+int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec);
 int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback);
 int sd_event_source_get_destroy_callback(sd_event_source *s, sd_event_destroy_t *ret);
 int sd_event_source_get_floating(sd_event_source *s);
@@ -170,6 +173,9 @@ int sd_event_source_set_ratelimit(sd_event_source *s, uint64_t interval_usec, un
 int sd_event_source_get_ratelimit(sd_event_source *s, uint64_t *ret_interval_usec, unsigned *ret_burst);
 int sd_event_source_is_ratelimited(sd_event_source *s);
 int sd_event_source_set_ratelimit_expire_callback(sd_event_source *s, sd_event_handler_t callback);
+int sd_event_source_leave_ratelimit(sd_event_source *s);
+
+int sd_event_trim_memory(void);
 
 /* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */
 _SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref);
diff --git a/src/libnm-systemd-shared/meson.build b/src/libnm-systemd-shared/meson.build
index d8e7a31a..b32bd7f6 100644
--- a/src/libnm-systemd-shared/meson.build
+++ b/src/libnm-systemd-shared/meson.build
@@ -22,6 +22,7 @@ libnm_systemd_shared = static_library(
     'src/basic/in-addr-util.c',
     'src/basic/inotify-util.c',
     'src/basic/io-util.c',
+    'src/basic/label.c',
     'src/basic/locale-util.c',
     'src/basic/memory-util.c',
     'src/basic/mempool.c',
@@ -42,7 +43,6 @@ libnm_systemd_shared = static_library(
     'src/basic/time-util.c',
     'src/basic/tmpfile-util.c',
     'src/basic/utf8.c',
-    'src/basic/util.c',
     'src/fundamental/sha256.c',
     'src/fundamental/string-util-fundamental.c',
     'src/shared/dns-domain.c',
diff --git a/src/libnm-systemd-core/sd-adapt-core/sd-device.h b/src/libnm-systemd-shared/sd-adapt-shared/argv-util.h
index 637892c2..637892c2 100644
--- a/src/libnm-systemd-core/sd-adapt-core/sd-device.h
+++ b/src/libnm-systemd-shared/sd-adapt-shared/argv-util.h
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/def.h b/src/libnm-systemd-shared/sd-adapt-shared/chase.h
index 637892c2..637892c2 100644
--- a/src/libnm-systemd-shared/sd-adapt-shared/def.h
+++ b/src/libnm-systemd-shared/sd-adapt-shared/chase.h
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h b/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/mallinfo-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h b/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/memstream-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h b/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/mountpoint-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h b/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h
index ee5a0a24..de6edd02 100644
--- a/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h
+++ b/src/libnm-systemd-shared/sd-adapt-shared/nm-sd-adapt-shared.h
@@ -28,12 +28,14 @@
 
 /*****************************************************************************/
 
-#ifndef VALGRIND
-#define VALGRIND 0
-#endif
+#define HAVE_VALGRIND_VALGRIND_H 0
 
 #define ENABLE_DEBUG_HASHMAP 0
 
+#define SD_BOOT 0
+
+#define HAVE_SPLIT_USR 0
+
 /*****************************************************************************
  * The remainder of the header is only enabled when building the systemd code
  * itself.
@@ -45,7 +47,7 @@
 #include <sys/ioctl.h>
 #include <pthread.h>
 
-#define ENABLE_GSHADOW FALSE
+#define ENABLE_GSHADOW 0
 
 #define HAVE_SECCOMP 0
 
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h b/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h
new file mode 100644
index 00000000..637892c2
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/psi-util.h
@@ -0,0 +1,3 @@
+#pragma once
+
+/* dummy header */
diff --git a/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h b/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h
new file mode 100644
index 00000000..97d1609d
--- /dev/null
+++ b/src/libnm-systemd-shared/sd-adapt-shared/unaligned-fundamental.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include "libnm-std-aux/unaligned-fundamental.h"
diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.c b/src/libnm-systemd-shared/src/basic/alloc-util.c
index e252b6ab..c07ab589 100644
--- a/src/libnm-systemd-shared/src/basic/alloc-util.c
+++ b/src/libnm-systemd-shared/src/basic/alloc-util.c
@@ -104,3 +104,7 @@ void* greedy_realloc0(
 
         return q;
 }
+
+void *expand_to_usable(void *ptr, size_t newsize _unused_) {
+        return ptr;
+}
diff --git a/src/libnm-systemd-shared/src/basic/alloc-util.h b/src/libnm-systemd-shared/src/basic/alloc-util.h
index b38db7d4..9a62381d 100644
--- a/src/libnm-systemd-shared/src/basic/alloc-util.h
+++ b/src/libnm-systemd-shared/src/basic/alloc-util.h
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <alloca.h>
+#include <malloc.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
@@ -14,6 +15,7 @@
 
 typedef void (*free_func_t)(void *p);
 typedef void* (*mfree_func_t)(void *p);
+typedef void (*free_array_func_t)(void *p, size_t n);
 
 /* If for some reason more than 4M are allocated on the stack, let's abort immediately. It's better than
  * proceeding and smashing the stack limits. Note that by default RLIMIT_STACK is 8M on Linux. */
@@ -184,17 +186,35 @@ void* greedy_realloc0(void **p, size_t need, size_t size);
 #  define msan_unpoison(r, s)
 #endif
 
-/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), in a way that
- * is compatible with _FORTIFY_SOURCES. If _FORTIFY_SOURCES is used many memory operations will take the
- * object size as returned by __builtin_object_size() into account. Hence, let's return the smaller size of
- * malloc_usable_size() and __builtin_object_size() here, so that we definitely operate in safe territory by
- * both the compiler's and libc's standards. Note that __builtin_object_size() evaluates to SIZE_MAX if the
- * size cannot be determined, hence the MIN() expression should be safe with dynamically sized memory,
- * too. Moreover, when NULL is passed malloc_usable_size() is documented to return zero, and
- * __builtin_object_size() returns SIZE_MAX too, hence we also return a sensible value of 0 in this corner
- * case. */
+/* Dummy allocator to tell the compiler that the new size of p is newsize. The implementation returns the
+ * pointer as is; the only reason for its existence is as a conduit for the _alloc_ attribute.  This must not
+ * be inlined (hence a non-static function with _noinline_ because LTO otherwise tries to inline it) because
+ * gcc then loses the attributes on the function.
+ * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96503 */
+void *expand_to_usable(void *p, size_t newsize) _alloc_(2) _returns_nonnull_ _noinline_;
+
+static inline size_t malloc_sizeof_safe(void **xp) {
+        if (_unlikely_(!xp || !*xp))
+                return 0;
+
+        size_t sz = malloc_usable_size(*xp);
+        *xp = expand_to_usable(*xp, sz);
+        /* GCC doesn't see the _returns_nonnull_ when built with ubsan, so yet another hint to make it doubly
+         * clear that expand_to_usable won't return NULL.
+         * See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79265 */
+        if (!*xp)
+                assert_not_reached();
+        return sz;
+}
+
+/* This returns the number of usable bytes in a malloc()ed region as per malloc_usable_size(), which may
+ * return a value larger than the size that was actually allocated. Access to that additional memory is
+ * discouraged because it violates the C standard; a compiler cannot see that this as valid. To help the
+ * compiler out, the MALLOC_SIZEOF_SAFE macro 'allocates' the usable size using a dummy allocator function
+ * expand_to_usable. There is a possibility of malloc_usable_size() returning different values during the
+ * lifetime of an object, which may cause problems, but the glibc allocator does not do that at the moment. */
 #define MALLOC_SIZEOF_SAFE(x) \
-        MIN(malloc_usable_size(x), __builtin_object_size(x, 0))
+        malloc_sizeof_safe((void**) &__builtin_choose_expr(__builtin_constant_p(x), (void*) { NULL }, (x)))
 
 /* Inspired by ELEMENTSOF() but operates on malloc()'ed memory areas: typesafely returns the number of items
  * that fit into the specified memory block */
diff --git a/src/libnm-systemd-shared/src/basic/cgroup-util.h b/src/libnm-systemd-shared/src/basic/cgroup-util.h
index df6d5b7b..9b30ae03 100644
--- a/src/libnm-systemd-shared/src/basic/cgroup-util.h
+++ b/src/libnm-systemd-shared/src/basic/cgroup-util.h
@@ -9,7 +9,7 @@
 #include <sys/statfs.h>
 #include <sys/types.h>
 
-#include "def.h"
+#include "constants.h"
 #include "set.h"
 
 #define SYSTEMD_CGROUP_CONTROLLER_LEGACY "name=systemd"
@@ -239,7 +239,6 @@ int cg_get_attribute_as_uint64(const char *controller, const char *path, const c
 /* Does a parse_boolean() on the attribute contents and sets ret accordingly */
 int cg_get_attribute_as_bool(const char *controller, const char *path, const char *attribute, bool *ret);
 
-int cg_set_access(const char *controller, const char *path, uid_t uid, gid_t gid);
 int cg_get_owner(const char *controller, const char *path, uid_t *ret_uid);
 
 int cg_set_xattr(const char *controller, const char *path, const char *name, const void *value, size_t size, int flags);
@@ -261,6 +260,7 @@ int cg_path_get_cgroupid(const char *path, uint64_t *ret);
 int cg_path_get_session(const char *path, char **session);
 int cg_path_get_owner_uid(const char *path, uid_t *uid);
 int cg_path_get_unit(const char *path, char **unit);
+int cg_path_get_unit_path(const char *path, char **unit);
 int cg_path_get_user_unit(const char *path, char **unit);
 int cg_path_get_machine_name(const char *path, char **machine);
 int cg_path_get_slice(const char *path, char **slice);
@@ -279,7 +279,8 @@ int cg_pid_get_user_slice(pid_t pid, char **slice);
 
 int cg_path_decode_unit(const char *cgroup, char **unit);
 
-char *cg_escape(const char *p);
+bool cg_needs_escape(const char *p);
+int cg_escape(const char *p, char **ret);
 char *cg_unescape(const char *p) _pure_;
 
 bool cg_controller_is_valid(const char *p);
diff --git a/src/libnm-systemd-shared/src/basic/constants.h b/src/libnm-systemd-shared/src/basic/constants.h
new file mode 100644
index 00000000..3f96786d
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/constants.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#if !defined(HAS_FEATURE_MEMORY_SANITIZER)
+#  if defined(__has_feature)
+#    if __has_feature(memory_sanitizer)
+#      define HAS_FEATURE_MEMORY_SANITIZER 1
+#    endif
+#  endif
+#  if !defined(HAS_FEATURE_MEMORY_SANITIZER)
+#    define HAS_FEATURE_MEMORY_SANITIZER 0
+#  endif
+#endif
+
+#if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
+#  ifdef __SANITIZE_ADDRESS__
+#      define HAS_FEATURE_ADDRESS_SANITIZER 1
+#  elif defined(__has_feature)
+#    if __has_feature(address_sanitizer)
+#      define HAS_FEATURE_ADDRESS_SANITIZER 1
+#    endif
+#  endif
+#  if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
+#    define HAS_FEATURE_ADDRESS_SANITIZER 0
+#  endif
+#endif
+
+#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC)
+
+/* Many different things, but also system unit start/stop */
+#define DEFAULT_TIMEOUT_USEC (DEFAULT_TIMEOUT_SEC*USEC_PER_SEC)
+/* User unit start/stop */
+#define DEFAULT_USER_TIMEOUT_USEC (DEFAULT_USER_TIMEOUT_SEC*USEC_PER_SEC)
+/* Timeout for user confirmation on the console */
+#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC)
+
+/* We use an extra-long timeout for the reload. This is because a reload or reexec means generators are rerun
+ * which are timed out after DEFAULT_TIMEOUT_USEC. Let's use twice that time here, so that the generators can
+ * have their timeout, and for everything else there's the same time budget in place. */
+#define DAEMON_RELOAD_TIMEOUT_SEC (DEFAULT_TIMEOUT_USEC * 2)
+
+#define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC)
+#define DEFAULT_START_LIMIT_BURST 5
+
+/* Wait for 1.5 seconds at maximum for freeze operation */
+#define FREEZE_TIMEOUT (1500 * USEC_PER_MSEC)
+
+/* The default time after which exit-on-idle services exit. This
+ * should be kept lower than the watchdog timeout, because otherwise
+ * the watchdog pings will keep the loop busy. */
+#define DEFAULT_EXIT_USEC (30*USEC_PER_SEC)
+
+/* The default value for the net.unix.max_dgram_qlen sysctl */
+#define DEFAULT_UNIX_MAX_DGRAM_QLEN 512
+
+#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT
+#define SIGNALS_IGNORE SIGPIPE
+
+#define NOTIFY_FD_MAX 768
+#define NOTIFY_BUFFER_MAX PIPE_BUF
+
+#if HAVE_SPLIT_USR
+#  define _CONF_PATHS_SPLIT_USR_NULSTR(n) "/lib/" n "\0"
+#  define _CONF_PATHS_SPLIT_USR(n) , "/lib/" n
+#else
+#  define _CONF_PATHS_SPLIT_USR_NULSTR(n)
+#  define _CONF_PATHS_SPLIT_USR(n)
+#endif
+
+/* Return a nulstr for a standard cascade of configuration paths, suitable to pass to
+ * conf_files_list_nulstr() to implement drop-in directories for extending configuration files. */
+#define CONF_PATHS_NULSTR(n)                    \
+        "/etc/" n "\0"                          \
+        "/run/" n "\0"                          \
+        "/usr/local/lib/" n "\0"                \
+        "/usr/lib/" n "\0"                      \
+        _CONF_PATHS_SPLIT_USR_NULSTR(n)
+
+#define CONF_PATHS_USR(n)                       \
+        "/etc/" n,                              \
+        "/run/" n,                              \
+        "/usr/local/lib/" n,                    \
+        "/usr/lib/" n
+
+#define CONF_PATHS(n)                           \
+        CONF_PATHS_USR(n)                       \
+        _CONF_PATHS_SPLIT_USR(n)
+
+#define CONF_PATHS_USR_STRV(n)                  \
+        STRV_MAKE(CONF_PATHS_USR(n))
+
+#define CONF_PATHS_STRV(n)                      \
+        STRV_MAKE(CONF_PATHS(n))
+
+/* The limit for PID 1 itself (which is not inherited to children) */
+#define HIGH_RLIMIT_MEMLOCK (1024ULL*1024ULL*64ULL)
+
+/* Since kernel 5.16 the kernel default limit was raised to 8M. Let's adjust things on old kernels too, and
+ * in containers so that our children inherit that. */
+#define DEFAULT_RLIMIT_MEMLOCK (1024ULL*1024ULL*8ULL)
+
+#define PLYMOUTH_SOCKET {                                       \
+                .un.sun_family = AF_UNIX,                       \
+                .un.sun_path = "\0/org/freedesktop/plymouthd",  \
+        }
+
+/* Path where PID1 listens for varlink subscriptions from systemd-oomd to notify of changes in ManagedOOM settings. */
+#define VARLINK_ADDR_PATH_MANAGED_OOM_SYSTEM "/run/systemd/io.system.ManagedOOM"
+/* Path where systemd-oomd listens for varlink connections from user managers to report changes in ManagedOOM settings. */
+#define VARLINK_ADDR_PATH_MANAGED_OOM_USER "/run/systemd/oom/io.system.ManagedOOM"
+
+#define KERNEL_BASELINE_VERSION "4.15"
diff --git a/src/libnm-systemd-shared/src/basic/env-file.c b/src/libnm-systemd-shared/src/basic/env-file.c
index 266e4e79..db270bed 100644
--- a/src/libnm-systemd-shared/src/basic/env-file.c
+++ b/src/libnm-systemd-shared/src/basic/env-file.c
@@ -14,11 +14,17 @@
 #include "tmpfile-util.h"
 #include "utf8.h"
 
+typedef int (*push_env_func_t)(
+                const char *filename,
+                unsigned line,
+                const char *key,
+                char *value,
+                void *userdata);
+
 static int parse_env_file_internal(
                 FILE *f,
                 const char *fname,
-                int (*push) (const char *filename, unsigned line,
-                             const char *key, char *value, void *userdata),
+                push_env_func_t push,
                 void *userdata) {
 
         size_t n_key = 0, n_value = 0, last_value_whitespace = SIZE_MAX, last_key_whitespace = SIZE_MAX;
@@ -39,6 +45,9 @@ static int parse_env_file_internal(
                 COMMENT_ESCAPE
         } state = PRE_KEY;
 
+        assert(f || fname);
+        assert(push);
+
         if (f)
                 r = read_full_stream(f, &contents, NULL);
         else
@@ -276,6 +285,8 @@ static int check_utf8ness_and_warn(
                 const char *filename, unsigned line,
                 const char *key, char *value) {
 
+        assert(key);
+
         if (!utf8_is_valid(key)) {
                 _cleanup_free_ char *p = NULL;
 
@@ -306,6 +317,8 @@ static int parse_env_file_push(
         va_list aq, *ap = userdata;
         int r;
 
+        assert(key);
+
         r = check_utf8ness_and_warn(filename, line, key, value);
         if (r < 0)
                 return r;
@@ -319,8 +332,7 @@ static int parse_env_file_push(
 
                 if (streq(key, k)) {
                         va_end(aq);
-                        free(*v);
-                        *v = value;
+                        free_and_replace(*v, value);
 
                         return 1;
                 }
@@ -340,6 +352,26 @@ int parse_env_filev(
         int r;
         va_list aq;
 
+        assert(f || fname);
+
+        va_copy(aq, ap);
+        r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
+        va_end(aq);
+        return r;
+}
+
+#if 0 /* NM_IGNORED */
+int parse_env_file_fdv(int fd, const char *fname, va_list ap) {
+        _cleanup_fclose_ FILE *f = NULL;
+        va_list aq;
+        int r;
+
+        assert(fd >= 0);
+
+        r = fdopen_independent(fd, "re", &f);
+        if (r < 0)
+                return r;
+
         va_copy(aq, ap);
         r = parse_env_file_internal(f, fname, parse_env_file_push, &aq);
         va_end(aq);
@@ -354,6 +386,8 @@ int parse_env_file_sentinel(
         va_list ap;
         int r;
 
+        assert(f || fname);
+
         va_start(ap, fname);
         r = parse_env_filev(f, fname, ap);
         va_end(ap);
@@ -361,15 +395,34 @@ int parse_env_file_sentinel(
         return r;
 }
 
-#if 0 /* NM_IGNORED */
+int parse_env_file_fd_sentinel(
+                int fd,
+                const char *fname, /* only used for logging */
+                ...) {
+
+        va_list ap;
+        int r;
+
+        assert(fd >= 0);
+
+        va_start(ap, fname);
+        r = parse_env_file_fdv(fd, fname, ap);
+        va_end(ap);
+
+        return r;
+}
+
 static int load_env_file_push(
                 const char *filename, unsigned line,
                 const char *key, char *value,
                 void *userdata) {
+
         char ***m = userdata;
         char *p;
         int r;
 
+        assert(key);
+
         r = check_utf8ness_and_warn(filename, line, key, value);
         if (r < 0)
                 return r;
@@ -386,15 +439,18 @@ static int load_env_file_push(
         return 0;
 }
 
-int load_env_file(FILE *f, const char *fname, char ***rl) {
+int load_env_file(FILE *f, const char *fname, char ***ret) {
         _cleanup_strv_free_ char **m = NULL;
         int r;
 
+        assert(f || fname);
+        assert(ret);
+
         r = parse_env_file_internal(f, fname, load_env_file_push, &m);
         if (r < 0)
                 return r;
 
-        *rl = TAKE_PTR(m);
+        *ret = TAKE_PTR(m);
         return 0;
 }
 
@@ -406,6 +462,8 @@ static int load_env_file_push_pairs(
         char ***m = ASSERT_PTR(userdata);
         int r;
 
+        assert(key);
+
         r = check_utf8ness_and_warn(filename, line, key, value);
         if (r < 0)
                 return r;
@@ -429,18 +487,34 @@ static int load_env_file_push_pairs(
                 return strv_extend(m, "");
 }
 
-int load_env_file_pairs(FILE *f, const char *fname, char ***rl) {
+int load_env_file_pairs(FILE *f, const char *fname, char ***ret) {
         _cleanup_strv_free_ char **m = NULL;
         int r;
 
+        assert(f || fname);
+        assert(ret);
+
         r = parse_env_file_internal(f, fname, load_env_file_push_pairs, &m);
         if (r < 0)
                 return r;
 
-        *rl = TAKE_PTR(m);
+        *ret = TAKE_PTR(m);
         return 0;
 }
 
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret) {
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        assert(fd >= 0);
+
+        r = fdopen_independent(fd, "re", &f);
+        if (r < 0)
+                return r;
+
+        return load_env_file_pairs(f, fname, ret);
+}
+
 static int merge_env_file_push(
                 const char *filename, unsigned line,
                 const char *key, char *value,
@@ -449,6 +523,8 @@ static int merge_env_file_push(
         char ***env = ASSERT_PTR(userdata);
         char *expanded_value;
 
+        assert(key);
+
         if (!value) {
                 log_error("%s:%u: invalid syntax (around \"%s\"), ignoring.", strna(filename), line, key);
                 return 0;
@@ -479,6 +555,9 @@ int merge_env_file(
                 FILE *f,
                 const char *fname) {
 
+        assert(env);
+        assert(f || fname);
+
         /* NOTE: this function supports braceful and braceless variable expansions,
          * plus "extended" substitutions, unlike other exported parsing functions.
          */
@@ -489,6 +568,9 @@ int merge_env_file(
 static void write_env_var(FILE *f, const char *v) {
         const char *p;
 
+        assert(f);
+        assert(v);
+
         p = strchr(v, '=');
         if (!p) {
                 /* Fallback */
@@ -517,14 +599,15 @@ static void write_env_var(FILE *f, const char *v) {
         fputc_unlocked('\n', f);
 }
 
-int write_env_file(const char *fname, char **l) {
+int write_env_file_at(int dir_fd, const char *fname, char **l) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *p = NULL;
         int r;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(fname);
 
-        r = fopen_temporary(fname, &f, &p);
+        r = fopen_temporary_at(dir_fd, fname, &f, &p);
         if (r < 0)
                 return r;
 
@@ -535,13 +618,13 @@ int write_env_file(const char *fname, char **l) {
 
         r = fflush_and_check(f);
         if (r >= 0) {
-                if (rename(p, fname) >= 0)
+                if (renameat(dir_fd, p, dir_fd, fname) >= 0)
                         return 0;
 
                 r = -errno;
         }
 
-        (void) unlink(p);
+        (void) unlinkat(dir_fd, p, 0);
         return r;
 }
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/env-file.h b/src/libnm-systemd-shared/src/basic/env-file.h
index de475885..2465eedd 100644
--- a/src/libnm-systemd-shared/src/basic/env-file.h
+++ b/src/libnm-systemd-shared/src/basic/env-file.h
@@ -1,17 +1,25 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
 #include <stdarg.h>
 #include <stdio.h>
 
 #include "macro.h"
 
 int parse_env_filev(FILE *f, const char *fname, va_list ap);
+int parse_env_file_fdv(int fd, const char *fname, va_list ap);
 int parse_env_file_sentinel(FILE *f, const char *fname, ...) _sentinel_;
 #define parse_env_file(f, fname, ...) parse_env_file_sentinel(f, fname, __VA_ARGS__, NULL)
-int load_env_file(FILE *f, const char *fname, char ***l);
-int load_env_file_pairs(FILE *f, const char *fname, char ***l);
+int parse_env_file_fd_sentinel(int fd, const char *fname, ...) _sentinel_;
+#define parse_env_file_fd(fd, fname, ...) parse_env_file_fd_sentinel(fd, fname, __VA_ARGS__, NULL)
+int load_env_file(FILE *f, const char *fname, char ***ret);
+int load_env_file_pairs(FILE *f, const char *fname, char ***ret);
+int load_env_file_pairs_fd(int fd, const char *fname, char ***ret);
 
 int merge_env_file(char ***env, FILE *f, const char *fname);
 
-int write_env_file(const char *fname, char **l);
+int write_env_file_at(int dir_fd, const char *fname, char **l);
+static inline int write_env_file(const char *fname, char **l) {
+        return write_env_file_at(AT_FDCWD, fname, l);
+}
diff --git a/src/libnm-systemd-shared/src/basic/env-util.c b/src/libnm-systemd-shared/src/basic/env-util.c
index f58d86a7..fa2753bc 100644
--- a/src/libnm-systemd-shared/src/basic/env-util.c
+++ b/src/libnm-systemd-shared/src/basic/env-util.c
@@ -139,20 +139,20 @@ bool strv_env_name_or_assignment_is_valid(char **l) {
         return true;
 }
 
-static int env_append(char **r, char ***k, char **a) {
-        assert(r);
+static int env_append(char **e, char ***k, char **a) {
+        assert(e);
         assert(k);
-        assert(*k >= r);
+        assert(*k >= e);
 
         if (!a)
                 return 0;
 
-        /* Expects the following arguments: 'r' shall point to the beginning of an strv we are going to append to, 'k'
+        /* Expects the following arguments: 'e' shall point to the beginning of an strv we are going to append to, 'k'
          * to a pointer pointing to the NULL entry at the end of the same array. 'a' shall point to another strv.
          *
-         * This call adds every entry of 'a' to 'r', either overriding an existing matching entry, or appending to it.
+         * This call adds every entry of 'a' to 'e', either overriding an existing matching entry, or appending to it.
          *
-         * This call assumes 'r' has enough pre-allocated space to grow by all of 'a''s items. */
+         * This call assumes 'e' has enough pre-allocated space to grow by all of 'a''s items. */
 
         for (; *a; a++) {
                 char **j, *c;
@@ -162,7 +162,7 @@ static int env_append(char **r, char ***k, char **a) {
                 if ((*a)[n] == '=')
                         n++;
 
-                for (j = r; j < *k; j++)
+                for (j = e; j < *k; j++)
                         if (strneq(*j, *a, n))
                                 break;
 
@@ -269,7 +269,7 @@ static bool env_entry_has_name(const char *entry, const char *name) {
 
 char **strv_env_delete(char **x, size_t n_lists, ...) {
         size_t n, i = 0;
-        char **r;
+        _cleanup_strv_free_ char **t = NULL;
         va_list ap;
 
         /* Deletes every entry from x that is mentioned in the other
@@ -277,8 +277,8 @@ char **strv_env_delete(char **x, size_t n_lists, ...) {
 
         n = strv_length(x);
 
-        r = new(char*, n+1);
-        if (!r)
+        t = new(char*, n+1);
+        if (!t)
                 return NULL;
 
         STRV_FOREACH(k, x) {
@@ -293,11 +293,9 @@ char **strv_env_delete(char **x, size_t n_lists, ...) {
                 }
                 va_end(ap);
 
-                r[i] = strdup(*k);
-                if (!r[i]) {
-                        strv_free(r);
+                t[i] = strdup(*k);
+                if (!t[i])
                         return NULL;
-                }
 
                 i++;
                 continue;
@@ -306,11 +304,11 @@ char **strv_env_delete(char **x, size_t n_lists, ...) {
                 va_end(ap);
         }
 
-        r[i] = NULL;
+        t[i] = NULL;
 
         assert(i <= n);
 
-        return r;
+        return TAKE_PTR(t);
 }
 
 char **strv_env_unset(char **l, const char *p) {
@@ -462,6 +460,48 @@ int strv_env_assign(char ***l, const char *key, const char *value) {
         return strv_env_replace_consume(l, p);
 }
 
+int _strv_env_assign_many(char ***l, ...) {
+        va_list ap;
+        int r;
+
+        assert(l);
+
+        va_start(ap, l);
+        for (;;) {
+                const char *key, *value;
+
+                key = va_arg(ap, const char *);
+                if (!key)
+                        break;
+
+                if (!env_name_is_valid(key)) {
+                        va_end(ap);
+                        return -EINVAL;
+                }
+
+                value = va_arg(ap, const char *);
+                if (!value) {
+                        strv_env_unset(*l, key);
+                        continue;
+                }
+
+                char *p = strjoin(key, "=", value);
+                if (!p) {
+                        va_end(ap);
+                        return -ENOMEM;
+                }
+
+                r = strv_env_replace_consume(l, p);
+                if (r < 0) {
+                        va_end(ap);
+                        return r;
+                }
+        }
+        va_end(ap);
+
+        return 0;
+}
+
 char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) {
         assert(name);
 
@@ -488,6 +528,7 @@ char *strv_env_get(char **l, const char *name) {
 
         return strv_env_get_n(l, name, strlen(name), 0);
 }
+#endif /* NM_IGNORED */
 
 char *strv_env_pairs_get(char **l, const char *name) {
         char *result = NULL;
@@ -501,6 +542,7 @@ char *strv_env_pairs_get(char **l, const char *name) {
         return result;
 }
 
+#if 0 /* NM_IGNORED */
 char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
         int k = 0;
 
@@ -549,7 +591,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
         const char *e, *word = format, *test_value = NULL; /* test_value is initialized to appease gcc */
         char *k;
-        _cleanup_free_ char *r = NULL;
+        _cleanup_free_ char *s = NULL;
         size_t i, len = 0; /* len is initialized to appease gcc */
         int nest = 0;
 
@@ -565,31 +607,31 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                 case CURLY:
                         if (*e == '{') {
-                                k = strnappend(r, word, e-word-1);
+                                k = strnappend(s, word, e-word-1);
                                 if (!k)
                                         return NULL;
 
-                                free_and_replace(r, k);
+                                free_and_replace(s, k);
 
                                 word = e-1;
                                 state = VARIABLE;
                                 nest++;
                         } else if (*e == '$') {
-                                k = strnappend(r, word, e-word);
+                                k = strnappend(s, word, e-word);
                                 if (!k)
                                         return NULL;
 
-                                free_and_replace(r, k);
+                                free_and_replace(s, k);
 
                                 word = e+1;
                                 state = WORD;
 
                         } else if (flags & REPLACE_ENV_ALLOW_BRACELESS && strchr(VALID_BASH_ENV_NAME_CHARS, *e)) {
-                                k = strnappend(r, word, e-word-1);
+                                k = strnappend(s, word, e-word-1);
                                 if (!k)
                                         return NULL;
 
-                                free_and_replace(r, k);
+                                free_and_replace(s, k);
 
                                 word = e-1;
                                 state = VARIABLE_RAW;
@@ -604,7 +646,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                                 t = strv_env_get_n(env, word+2, e-word-2, flags);
 
-                                if (!strextend(&r, t))
+                                if (!strextend(&s, t))
                                         return NULL;
 
                                 word = e+1;
@@ -657,7 +699,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                                 else if (!t && state == DEFAULT_VALUE)
                                         t = v = replace_env_n(test_value, e-test_value, env, flags);
 
-                                if (!strextend(&r, t))
+                                if (!strextend(&s, t))
                                         return NULL;
 
                                 word = e+1;
@@ -673,7 +715,7 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
 
                                 t = strv_env_get_n(env, word+1, e-word-1, flags);
 
-                                if (!strextend(&r, t))
+                                if (!strextend(&s, t))
                                         return NULL;
 
                                 word = e--;
@@ -689,13 +731,13 @@ char *replace_env_n(const char *format, size_t n, char **env, unsigned flags) {
                 assert(flags & REPLACE_ENV_ALLOW_BRACELESS);
 
                 t = strv_env_get_n(env, word+1, e-word-1, flags);
-                return strjoin(r, t);
+                return strjoin(s, t);
         } else
-                return strnappend(r, word, e-word);
+                return strnappend(s, word, e-word);
 }
 
 char **replace_env_argv(char **argv, char **env) {
-        char **ret;
+        _cleanup_strv_free_ char **ret = NULL;
         size_t k = 0, l = 0;
 
         l = strv_length(argv);
@@ -709,7 +751,8 @@ char **replace_env_argv(char **argv, char **env) {
                 /* If $FOO appears as single word, replace it by the split up variable */
                 if ((*i)[0] == '$' && !IN_SET((*i)[1], '{', '$')) {
                         char *e;
-                        char **w, **m = NULL;
+                        char **w;
+                        _cleanup_strv_free_ char **m = NULL;
                         size_t q;
 
                         e = strv_env_get(env, *i+1);
@@ -719,11 +762,9 @@ char **replace_env_argv(char **argv, char **env) {
                                 r = strv_split_full(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_UNQUOTE);
                                 if (r < 0) {
                                         ret[k] = NULL;
-                                        strv_free(ret);
                                         return NULL;
                                 }
-                        } else
-                                m = NULL;
+                        }
 
                         q = strv_length(m);
                         l = l + q - 1;
@@ -731,15 +772,13 @@ char **replace_env_argv(char **argv, char **env) {
                         w = reallocarray(ret, l + 1, sizeof(char *));
                         if (!w) {
                                 ret[k] = NULL;
-                                strv_free(ret);
-                                strv_free(m);
                                 return NULL;
                         }
 
                         ret = w;
                         if (m) {
                                 memcpy(ret + k, m, q * sizeof(char*));
-                                free(m);
+                                m = mfree(m);
                         }
 
                         k += q;
@@ -748,15 +787,13 @@ char **replace_env_argv(char **argv, char **env) {
 
                 /* If ${FOO} appears as part of a word, replace it by the variable as-is */
                 ret[k] = replace_env(*i, env, 0);
-                if (!ret[k]) {
-                        strv_free(ret);
+                if (!ret[k])
                         return NULL;
-                }
                 k++;
         }
 
         ret[k] = NULL;
-        return ret;
+        return TAKE_PTR(ret);
 }
 #endif /* NM_IGNORED */
 
diff --git a/src/libnm-systemd-shared/src/basic/env-util.h b/src/libnm-systemd-shared/src/basic/env-util.h
index b927ac7a..b0ff5a11 100644
--- a/src/libnm-systemd-shared/src/basic/env-util.h
+++ b/src/libnm-systemd-shared/src/basic/env-util.h
@@ -49,6 +49,8 @@ int strv_env_replace_consume(char ***l, char *p); /* In place ... */
 int strv_env_replace_strdup(char ***l, const char *assignment);
 int strv_env_replace_strdup_passthrough(char ***l, const char *assignment);
 int strv_env_assign(char ***l, const char *key, const char *value);
+int _strv_env_assign_many(char ***l, ...) _sentinel_;
+#define strv_env_assign_many(l, ...) _strv_env_assign_many(l, __VA_ARGS__, NULL)
 
 char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
 char *strv_env_get(char **x, const char *n) _pure_;
diff --git a/src/libnm-systemd-shared/src/basic/escape.c b/src/libnm-systemd-shared/src/basic/escape.c
index cc317dfb..6d2c1d4d 100644
--- a/src/libnm-systemd-shared/src/basic/escape.c
+++ b/src/libnm-systemd-shared/src/basic/escape.c
@@ -448,44 +448,51 @@ char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFl
 }
 
 char* octescape(const char *s, size_t len) {
-        char *r, *t;
-        const char *f;
+        char *buf, *t;
 
-        /* Escapes all chars in bad, in addition to \ and " chars,
-         * in \nnn style escaping. */
+        /* Escapes all chars in bad, in addition to \ and " chars, in \nnn style escaping. */
 
-        r = new(char, len * 4 + 1);
-        if (!r)
+        assert(s || len == 0);
+
+        t = buf = new(char, len * 4 + 1);
+        if (!buf)
                 return NULL;
 
-        for (f = s, t = r; f < s + len; f++) {
+        for (size_t i = 0; i < len; i++) {
+                uint8_t u = (uint8_t) s[i];
 
-                if (*f < ' ' || *f >= 127 || IN_SET(*f, '\\', '"')) {
+                if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"')) {
                         *(t++) = '\\';
-                        *(t++) = '0' + (*f >> 6);
-                        *(t++) = '0' + ((*f >> 3) & 8);
-                        *(t++) = '0' + (*f & 8);
+                        *(t++) = '0' + (u >> 6);
+                        *(t++) = '0' + ((u >> 3) & 7);
+                        *(t++) = '0' + (u & 7);
                 } else
-                        *(t++) = *f;
+                        *(t++) = u;
         }
 
         *t = 0;
-
-        return r;
-
+        return buf;
 }
 
 static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) {
         assert(bad);
+        assert(t);
+        assert(s);
 
-        for (; *s; s++)
-                if (char_is_cc(*s))
-                        t += cescape_char(*s, t);
-                else {
+        while (*s) {
+                int l = utf8_encoded_valid_unichar(s, SIZE_MAX);
+
+                if (char_is_cc(*s) || l < 0)
+                        t += cescape_char(*(s++), t);
+                else if (l == 1) {
                         if (*s == '\\' || strchr(bad, *s))
                                 *(t++) = '\\';
-                        *(t++) = *s;
+                        *(t++) = *(s++);
+                } else {
+                        t = mempcpy(t, s, l);
+                        s += l;
                 }
+        }
 
         return t;
 }
@@ -514,11 +521,16 @@ char* shell_maybe_quote(const char *s, ShellEscapeFlags flags) {
         if (FLAGS_SET(flags, SHELL_ESCAPE_EMPTY) && isempty(s))
                 return strdup("\"\""); /* We don't use $'' here in the POSIX mode. "" is fine too. */
 
-        for (p = s; *p; p++)
-                if (char_is_cc(*p) ||
+        for (p = s; *p; ) {
+                int l = utf8_encoded_valid_unichar(p, SIZE_MAX);
+
+                if (char_is_cc(*p) || l < 0 ||
                     strchr(WHITESPACE SHELL_NEED_QUOTES, *p))
                         break;
 
+                p += l;
+        }
+
         if (!*p)
                 return strdup(s);
 
diff --git a/src/libnm-systemd-shared/src/basic/fd-util.c b/src/libnm-systemd-shared/src/basic/fd-util.c
index 61a5bb78..a0e2f4eb 100644
--- a/src/libnm-systemd-shared/src/basic/fd-util.c
+++ b/src/libnm-systemd-shared/src/basic/fd-util.c
@@ -23,6 +23,7 @@
 #include "missing_fcntl.h"
 #include "missing_fs.h"
 #include "missing_syscall.h"
+#include "mountpoint-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
@@ -31,7 +32,6 @@
 #include "stat-util.h"
 #include "stdio-util.h"
 #include "tmpfile-util.h"
-#include "util.h"
 
 /* The maximum number of iterations in the loop to close descriptors in the fallback case
  * when /proc/self/fd/ is inaccessible. */
@@ -59,11 +59,9 @@ int close_nointr(int fd) {
 }
 
 int safe_close(int fd) {
-
         /*
-         * Like close_nointr() but cannot fail. Guarantees errno is
-         * unchanged. Is a NOP with negative fds passed, and returns
-         * -1, so that it can be used in this syntax:
+         * Like close_nointr() but cannot fail. Guarantees errno is unchanged. Is a noop for negative fds,
+         * and returns -EBADF, so that it can be used in this syntax:
          *
          * fd = safe_close(fd);
          */
@@ -79,7 +77,7 @@ int safe_close(int fd) {
                 assert_se(close_nointr(fd) != -EBADF);
         }
 
-        return -1;
+        return -EBADF;
 }
 
 void safe_close_pair(int p[static 2]) {
@@ -177,12 +175,35 @@ int fd_cloexec(int fd, bool cloexec) {
 }
 
 #if 0 /* NM_IGNORED */
+int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec) {
+        int ret = 0, r;
+
+        assert(n_fds == 0 || fds);
+
+        for (size_t i = 0; i < n_fds; i++) {
+                if (fds[i] < 0) /* Skip gracefully over already invalidated fds */
+                        continue;
+
+                r = fd_cloexec(fds[i], cloexec);
+                if (r < 0 && ret >= 0) /* Continue going, but return first error */
+                        ret = r;
+                else
+                        ret = 1; /* report if we did anything */
+        }
+
+        return ret;
+}
+
 _pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) {
         assert(n_fdset == 0 || fdset);
 
-        for (size_t i = 0; i < n_fdset; i++)
+        for (size_t i = 0; i < n_fdset; i++) {
+                if (fdset[i] < 0)
+                        continue;
+
                 if (fdset[i] == fd)
                         return true;
+        }
 
         return false;
 }
@@ -229,7 +250,7 @@ static int close_all_fds_frugal(const int except[], size_t n_except) {
                                        "Refusing to loop over %d potential fds.",
                                        max_fd);
 
-        for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -1) {
+        for (int fd = 3; fd >= 0; fd = fd < max_fd ? fd + 1 : -EBADF) {
                 int q;
 
                 if (fd_in_set(fd, except, n_except))
@@ -255,6 +276,10 @@ static int close_all_fds_special_case(const int except[], size_t n_except) {
         if (!have_close_range)
                 return 0;
 
+        if (n_except == 1 && except[0] < 0) /* Minor optimization: if we only got one fd, and it's invalid,
+                                             * we got none */
+                n_except = 0;
+
         switch (n_except) {
 
         case 0:
@@ -389,12 +414,13 @@ int close_all_fds(const int except[], size_t n_except) {
                 return close_all_fds_frugal(except, n_except); /* ultimate fallback if /proc/ is not available */
 
         FOREACH_DIRENT(de, d, return -errno) {
-                int fd = -1, q;
+                int fd = -EBADF, q;
 
                 if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN))
                         continue;
 
-                if (safe_atoi(de->d_name, &fd) < 0)
+                fd = parse_fd(de->d_name);
+                if (fd < 0)
                         /* Let's better ignore this, just in case */
                         continue;
 
@@ -479,7 +505,8 @@ void cmsg_close_all(struct msghdr *mh) {
 
         CMSG_FOREACH(cmsg, mh)
                 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
-                        close_many((int*) ((void*) CMSG_DATA(cmsg)), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
+                        close_many(CMSG_TYPED_DATA(cmsg, int),
+                                   (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
 }
 
 bool fdname_is_valid(const char *s) {
@@ -513,6 +540,11 @@ bool fdname_is_valid(const char *s) {
 int fd_get_path(int fd, char **ret) {
         int r;
 
+        assert(fd >= 0 || fd == AT_FDCWD);
+
+        if (fd == AT_FDCWD)
+                return safe_getcwd(ret);
+
         r = readlink_malloc(FORMAT_PROC_FD_PATH(fd), ret);
         if (r == -ENOENT) {
                 /* ENOENT can mean two things: that the fd does not exist or that /proc is not mounted. Let's make
@@ -611,25 +643,23 @@ int fd_move_above_stdio(int fd) {
 
 #if 0 /* NM_IGNORED */
 int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd) {
-
-        int fd[3] = { /* Put together an array of fds we work on */
-                original_input_fd,
-                original_output_fd,
-                original_error_fd
-        };
-
-        int r, i,
-                null_fd = -1,                /* if we open /dev/null, we store the fd to it here */
-                copy_fd[3] = { -1, -1, -1 }; /* This contains all fds we duplicate here temporarily, and hence need to close at the end */
+        int fd[3] = { original_input_fd,             /* Put together an array of fds we work on */
+                      original_output_fd,
+                      original_error_fd },
+            null_fd = -EBADF,                        /* If we open /dev/null, we store the fd to it here */
+            copy_fd[3] = { -EBADF, -EBADF, -EBADF }, /* This contains all fds we duplicate here
+                                                      * temporarily, and hence need to close at the end. */
+            r;
         bool null_readable, null_writable;
 
-        /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors is
-         * specified as -1 it will be connected with /dev/null instead. If any of the file descriptors is passed as
-         * itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is turned off should it be
-         * on.
+        /* Sets up stdin, stdout, stderr with the three file descriptors passed in. If any of the descriptors
+         * is specified as -EBADF it will be connected with /dev/null instead. If any of the file descriptors
+         * is passed as itself (e.g. stdin as STDIN_FILENO) it is left unmodified, but the O_CLOEXEC bit is
+         * turned off should it be on.
          *
-         * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and on
-         * failure! Thus, callers should assume that when this function returns the input fds are invalidated.
+         * Note that if any of the passed file descriptors are > 2 they will be closed — both on success and
+         * on failure! Thus, callers should assume that when this function returns the input fds are
+         * invalidated.
          *
          * Note that when this function fails stdin/stdout/stderr might remain half set up!
          *
@@ -665,7 +695,7 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_
         }
 
         /* Let's assemble fd[] with the fds to install in place of stdin/stdout/stderr */
-        for (i = 0; i < 3; i++) {
+        for (int i = 0; i < 3; i++) {
 
                 if (fd[i] < 0)
                         fd[i] = null_fd;        /* A negative parameter means: connect this one to /dev/null */
@@ -681,10 +711,10 @@ int rearrange_stdio(int original_input_fd, int original_output_fd, int original_
                 }
         }
 
-        /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that we
-         * have freedom to move them around. If the fds already were at the right places then the specific fds are
-         * -1. Let's now move them to the right places. This is the point of no return. */
-        for (i = 0; i < 3; i++) {
+        /* At this point we now have the fds to use in fd[], and they are all above the stdio range, so that
+         * we have freedom to move them around. If the fds already were at the right places then the specific
+         * fds are -EBADF. Let's now move them to the right places. This is the point of no return. */
+        for (int i = 0; i < 3; i++) {
 
                 if (fd[i] == i) {
 
@@ -715,7 +745,7 @@ finish:
                 safe_close_above_stdio(original_error_fd);
 
         /* Close the copies we moved > 2 */
-        for (i = 0; i < 3; i++)
+        for (int i = 0; i < 3; i++)
                 safe_close(copy_fd[i]);
 
         /* Close our null fd, if it's > 2 */
@@ -723,27 +753,47 @@ finish:
 
         return r;
 }
+#endif /* NM_IGNORED */
 
 int fd_reopen(int fd, int flags) {
         int new_fd, r;
 
+        assert(fd >= 0 || fd == AT_FDCWD);
+
         /* Reopens the specified fd with new flags. This is useful for convert an O_PATH fd into a regular one, or to
          * turn O_RDWR fds into O_RDONLY fds.
          *
          * This doesn't work on sockets (since they cannot be open()ed, ever).
          *
-         * This implicitly resets the file read index to 0. */
-
-        if (FLAGS_SET(flags, O_DIRECTORY)) {
+         * This implicitly resets the file read index to 0.
+         *
+         * If AT_FDCWD is specified as file descriptor gets an fd to the current cwd.
+         *
+         * If the specified file descriptor refers to a symlink via O_PATH, then this function cannot be used
+         * to follow that symlink. Because we cannot have non-O_PATH fds to symlinks reopening it without
+         * O_PATH will always result in -ELOOP. Or in other words: if you have an O_PATH fd to a symlink you
+         * can reopen it only if you pass O_PATH again. */
+
+        if (FLAGS_SET(flags, O_NOFOLLOW))
+                /* O_NOFOLLOW is not allowed in fd_reopen(), because after all this is primarily implemented
+                 * via a symlink-based interface in /proc/self/fd. Let's refuse this here early. Note that
+                 * the kernel would generate ELOOP here too, hence this manual check is mostly redundant –
+                 * the only reason we add it here is so that the O_DIRECTORY special case (see below) behaves
+                 * the same way as the non-O_DIRECTORY case. */
+                return -ELOOP;
+
+        if (FLAGS_SET(flags, O_DIRECTORY) || fd == AT_FDCWD) {
                 /* If we shall reopen the fd as directory we can just go via "." and thus bypass the whole
                  * magic /proc/ directory, and make ourselves independent of that being mounted. */
-                new_fd = openat(fd, ".", flags);
+                new_fd = openat(fd, ".", flags | O_DIRECTORY);
                 if (new_fd < 0)
                         return -errno;
 
                 return new_fd;
         }
 
+        assert(fd >= 0);
+
         new_fd = open(FORMAT_PROC_FD_PATH(fd), flags);
         if (new_fd < 0) {
                 if (errno != ENOENT)
@@ -761,6 +811,50 @@ int fd_reopen(int fd, int flags) {
         return new_fd;
 }
 
+#if 0 /* NM_IGNORED */
+int fd_reopen_condition(
+                int fd,
+                int flags,
+                int mask,
+                int *ret_new_fd) {
+
+        int r, new_fd;
+
+        assert(fd >= 0);
+
+        /* Invokes fd_reopen(fd, flags), but only if the existing F_GETFL flags don't match the specified
+         * flags (masked by the specified mask). This is useful for converting O_PATH fds into real fds if
+         * needed, but only then. */
+
+        r = fcntl(fd, F_GETFL);
+        if (r < 0)
+                return -errno;
+
+        if ((r & mask) == (flags & mask)) {
+                *ret_new_fd = -EBADF;
+                return fd;
+        }
+
+        new_fd = fd_reopen(fd, flags);
+        if (new_fd < 0)
+                return new_fd;
+
+        *ret_new_fd = new_fd;
+        return new_fd;
+}
+
+int fd_is_opath(int fd) {
+        int r;
+
+        assert(fd >= 0);
+
+        r = fcntl(fd, F_GETFL);
+        if (r < 0)
+                return -errno;
+
+        return FLAGS_SET(r, O_PATH);
+}
+
 int read_nr_open(void) {
         _cleanup_free_ char *nr_open = NULL;
         int r;
@@ -805,4 +899,89 @@ int fd_get_diskseq(int fd, uint64_t *ret) {
 
         return 0;
 }
+
+int path_is_root_at(int dir_fd, const char *path) {
+        STRUCT_NEW_STATX_DEFINE(st);
+        STRUCT_NEW_STATX_DEFINE(pst);
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+
+        if (!isempty(path)) {
+                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC);
+                if (fd < 0)
+                        return -errno;
+
+                dir_fd = fd;
+        }
+
+        r = statx_fallback(dir_fd, ".", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st.sx);
+        if (r == -ENOTDIR)
+                return false;
+        if (r < 0)
+                return r;
+
+        r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx);
+        if (r < 0)
+                return r;
+
+        /* First, compare inode. If these are different, the fd does not point to the root directory "/". */
+        if (!statx_inode_same(&st.sx, &pst.sx))
+                return false;
+
+        /* Even if the parent directory has the same inode, the fd may not point to the root directory "/",
+         * and we also need to check that the mount ids are the same. Otherwise, a construct like the
+         * following could be used to trick us:
+         *
+         * $ mkdir /tmp/x /tmp/x/y
+         * $ mount --bind /tmp/x /tmp/x/y
+         *
+         * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old
+         * kernel is used without /proc mounted. In that case, let's assume that we do not have such spurious
+         * mount points in an early boot stage, and silently skip the following check. */
+
+        if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) {
+                int mntid;
+
+                r = path_get_mnt_id_at(dir_fd, "", &mntid);
+                if (r == -ENOSYS)
+                        return true; /* skip the mount ID check */
+                if (r < 0)
+                        return r;
+                assert(mntid >= 0);
+
+                st.nsx.stx_mnt_id = mntid;
+                st.nsx.stx_mask |= STATX_MNT_ID;
+        }
+
+        if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) {
+                int mntid;
+
+                r = path_get_mnt_id_at(dir_fd, "..", &mntid);
+                if (r == -ENOSYS)
+                        return true; /* skip the mount ID check */
+                if (r < 0)
+                        return r;
+                assert(mntid >= 0);
+
+                pst.nsx.stx_mnt_id = mntid;
+                pst.nsx.stx_mask |= STATX_MNT_ID;
+        }
+
+        return statx_mount_same(&st.nsx, &pst.nsx);
+}
+
+const char *accmode_to_string(int flags) {
+        switch (flags & O_ACCMODE) {
+        case O_RDONLY:
+                return "ro";
+        case O_WRONLY:
+                return "wo";
+        case O_RDWR:
+                return "rw";
+        default:
+                return NULL;
+        }
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/fd-util.h b/src/libnm-systemd-shared/src/basic/fd-util.h
index d9896e27..c870a1b8 100644
--- a/src/libnm-systemd-shared/src/basic/fd-util.h
+++ b/src/libnm-systemd-shared/src/basic/fd-util.h
@@ -2,6 +2,7 @@
 #pragma once
 
 #include <dirent.h>
+#include <fcntl.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <sys/socket.h>
@@ -15,14 +16,15 @@
 /* Make sure we can distinguish fd 0 and NULL */
 #define FD_TO_PTR(fd) INT_TO_PTR((fd)+1)
 #define PTR_TO_FD(p) (PTR_TO_INT(p)-1)
+#define PIPE_EBADF { -EBADF, -EBADF }
 
 int close_nointr(int fd);
 int safe_close(int fd);
 void safe_close_pair(int p[static 2]);
 
 static inline int safe_close_above_stdio(int fd) {
-        if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -1 */
-                return -1;
+        if (fd < 3) /* Don't close stdin/stdout/stderr, but still invalidate the fd by returning -EBADF. */
+                return -EBADF;
 
         return safe_close(fd);
 }
@@ -56,6 +58,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL);
 
 int fd_nonblock(int fd, bool nonblock);
 int fd_cloexec(int fd, bool cloexec);
+int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec);
 
 int get_max_fd(void);
 
@@ -72,30 +75,16 @@ int fd_get_path(int fd, char **ret);
 
 int move_fd(int from, int to, int cloexec);
 
-enum {
-        ACQUIRE_NO_DEV_NULL = 1 << 0,
-        ACQUIRE_NO_MEMFD    = 1 << 1,
-        ACQUIRE_NO_PIPE     = 1 << 2,
-        ACQUIRE_NO_TMPFILE  = 1 << 3,
-        ACQUIRE_NO_REGULAR  = 1 << 4,
-};
-
 int fd_move_above_stdio(int fd);
 
 int rearrange_stdio(int original_input_fd, int original_output_fd, int original_error_fd);
 
 static inline int make_null_stdio(void) {
-        return rearrange_stdio(-1, -1, -1);
+        return rearrange_stdio(-EBADF, -EBADF, -EBADF);
 }
 
-/* Like TAKE_PTR() but for file descriptors, resetting them to -1 */
-#define TAKE_FD(fd)                             \
-        ({                                      \
-                int *_fd_ = &(fd);              \
-                int _ret_ = *_fd_;              \
-                *_fd_ = -1;                     \
-                _ret_;                          \
-        })
+/* Like TAKE_PTR() but for file descriptors, resetting them to -EBADF */
+#define TAKE_FD(fd) TAKE_GENERIC(fd, int, -EBADF)
 
 /* Like free_and_replace(), but for file descriptors */
 #define close_and_replace(a, b)                 \
@@ -107,9 +96,19 @@ static inline int make_null_stdio(void) {
         })
 
 int fd_reopen(int fd, int flags);
+int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd);
+int fd_is_opath(int fd);
 int read_nr_open(void);
 int fd_get_diskseq(int fd, uint64_t *ret);
 
+int path_is_root_at(int dir_fd, const char *path);
+static inline int dir_fd_is_root(int dir_fd) {
+        return path_is_root_at(dir_fd, NULL);
+}
+static inline int dir_fd_is_root_or_cwd(int dir_fd) {
+        return dir_fd == AT_FDCWD ? true : path_is_root_at(dir_fd, NULL);
+}
+
 /* The maximum length a buffer for a /proc/self/fd/<fd> path needs */
 #define PROC_FD_PATH_MAX \
         (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int))
@@ -123,3 +122,13 @@ static inline char *format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int f
 
 #define FORMAT_PROC_FD_PATH(fd) \
         format_proc_fd_path((char[PROC_FD_PATH_MAX]) {}, (fd))
+
+const char *accmode_to_string(int flags);
+
+/* Like ASSERT_PTR, but for fds */
+#define ASSERT_FD(fd)                           \
+        ({                                      \
+                int _fd_ = (fd);                \
+                assert(_fd_ >= 0);              \
+                _fd_;                           \
+        })
diff --git a/src/libnm-systemd-shared/src/basic/fileio.c b/src/libnm-systemd-shared/src/basic/fileio.c
index d996bc02..908a0309 100644
--- a/src/libnm-systemd-shared/src/basic/fileio.c
+++ b/src/libnm-systemd-shared/src/basic/fileio.c
@@ -15,7 +15,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
@@ -23,6 +23,7 @@
 #include "log.h"
 #include "macro.h"
 #include "mkdir.h"
+#include "nulstr-util.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "socket-util.h"
@@ -45,19 +46,6 @@
  * can detect EOFs. */
 #define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U)
 
-int fopen_unlocked(const char *path, const char *options, FILE **ret) {
-        assert(ret);
-
-        FILE *f = fopen(path, options);
-        if (!f)
-                return -errno;
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
-
-        *ret = f;
-        return 0;
-}
-
 int fdopen_unlocked(int fd, const char *options, FILE **ret) {
         assert(ret);
 
@@ -80,7 +68,7 @@ int take_fdopen_unlocked(int *fd, const char *options, FILE **ret) {
         if (r < 0)
                 return r;
 
-        *fd = -1;
+        *fd = -EBADF;
 
         return 0;
 }
@@ -92,7 +80,7 @@ FILE* take_fdopen(int *fd, const char *options) {
         if (!f)
                 return NULL;
 
-        *fd = -1;
+        *fd = -EBADF;
 
         return f;
 }
@@ -104,7 +92,7 @@ DIR* take_fdopendir(int *dfd) {
         if (!d)
                 return NULL;
 
-        *dfd = -1;
+        *dfd = -EBADF;
 
         return d;
 }
@@ -137,7 +125,7 @@ int write_string_stream_ts(
                 const struct timespec *ts) {
 
         bool needs_nl;
-        int r, fd = -1;
+        int r, fd = -EBADF;
 
         assert(f);
         assert(line);
@@ -212,7 +200,8 @@ int write_string_stream_ts(
         return 0;
 }
 
-static int write_string_file_atomic(
+static int write_string_file_atomic_at(
+                int dir_fd,
                 const char *fn,
                 const char *line,
                 WriteStringFileFlags flags,
@@ -228,7 +217,7 @@ static int write_string_file_atomic(
         /* Note that we'd really like to use O_TMPFILE here, but can't really, since we want replacement
          * semantics here, and O_TMPFILE can't offer that. i.e. rename() replaces but linkat() doesn't. */
 
-        r = fopen_temporary(fn, &f, &p);
+        r = fopen_temporary_at(dir_fd, fn, &f, &p);
         if (r < 0)
                 return r;
 
@@ -240,7 +229,7 @@ static int write_string_file_atomic(
         if (r < 0)
                 goto fail;
 
-        if (rename(p, fn) < 0) {
+        if (renameat(dir_fd, p, dir_fd, fn) < 0) {
                 r = -errno;
                 goto fail;
         }
@@ -255,18 +244,20 @@ static int write_string_file_atomic(
         return 0;
 
 fail:
-        (void) unlink(p);
+        (void) unlinkat(dir_fd, p, 0);
         return r;
 }
 
-int write_string_file_ts(
+int write_string_file_ts_at(
+                int dir_fd,
                 const char *fn,
                 const char *line,
                 WriteStringFileFlags flags,
                 const struct timespec *ts) {
 
         _cleanup_fclose_ FILE *f = NULL;
-        int q, r, fd;
+        _cleanup_close_ int fd = -EBADF;
+        int q, r;
 
         assert(fn);
         assert(line);
@@ -275,7 +266,7 @@ int write_string_file_ts(
         assert(!((flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE) && (flags & WRITE_STRING_FILE_SYNC)));
 
         if (flags & WRITE_STRING_FILE_MKDIR_0755) {
-                r = mkdir_parents(fn, 0755);
+                r = mkdirat_parents(dir_fd, fn, 0755);
                 if (r < 0)
                         return r;
         }
@@ -283,7 +274,7 @@ int write_string_file_ts(
         if (flags & WRITE_STRING_FILE_ATOMIC) {
                 assert(flags & WRITE_STRING_FILE_CREATE);
 
-                r = write_string_file_atomic(fn, line, flags, ts);
+                r = write_string_file_atomic_at(dir_fd, fn, line, flags, ts);
                 if (r < 0)
                         goto fail;
 
@@ -292,22 +283,20 @@ int write_string_file_ts(
                 assert(!ts);
 
         /* We manually build our own version of fopen(..., "we") that works without O_CREAT and with O_NOFOLLOW if needed. */
-        fd = open(fn, O_CLOEXEC|O_NOCTTY |
-                  (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) |
-                  (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) |
-                  (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) |
-                  (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY),
-                  (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666));
+        fd = openat(dir_fd, fn, O_CLOEXEC|O_NOCTTY |
+                    (FLAGS_SET(flags, WRITE_STRING_FILE_NOFOLLOW) ? O_NOFOLLOW : 0) |
+                    (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) |
+                    (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) |
+                    (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY),
+                    (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666));
         if (fd < 0) {
                 r = -errno;
                 goto fail;
         }
 
-        r = fdopen_unlocked(fd, "w", &f);
-        if (r < 0) {
-                safe_close(fd);
+        r = take_fdopen_unlocked(&fd, "w", &f);
+        if (r < 0)
                 goto fail;
-        }
 
         if (flags & WRITE_STRING_FILE_DISABLE_BUFFER)
                 setvbuf(f, NULL, _IONBF, 0);
@@ -353,21 +342,22 @@ int write_string_filef(
         return write_string_file(fn, p, flags);
 }
 
-int read_one_line_file(const char *fn, char **line) {
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret) {
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
-        assert(fn);
-        assert(line);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(filename);
+        assert(ret);
 
-        r = fopen_unlocked(fn, "re", &f);
+        r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f);
         if (r < 0)
                 return r;
 
-        return read_line(f, LONG_LINE_MAX, line);
+        return read_line(f, LONG_LINE_MAX, ret);
 }
 
-int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
+int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) {
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *buf = NULL;
         size_t l, k;
@@ -385,7 +375,7 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
         if (!buf)
                 return -ENOMEM;
 
-        r = fopen_unlocked(fn, "re", &f);
+        r = fopen_unlocked_at(dir_fd, fn, "re", 0, &f);
         if (r < 0)
                 return r;
 
@@ -504,7 +494,7 @@ int read_virtual_file_fd(int fd, size_t max_size, char **ret_contents, size_t *r
                  * at least one more byte to be able to distinguish EOF from truncation. */
                 if (max_size != SIZE_MAX && n > max_size) {
                         n = size; /* Make sure we never use more than what we sized the buffer for (so that
-                                   * we have one free byte in it for the trailing NUL we add below).*/
+                                   * we have one free byte in it for the trailing NUL we add below). */
                         truncated = true;
                         break;
                 }
@@ -558,7 +548,7 @@ int read_virtual_file_at(
                 char **ret_contents,
                 size_t *ret_size) {
 
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
 
@@ -760,62 +750,19 @@ int read_full_file_full(
                 size_t *ret_size) {
 
         _cleanup_fclose_ FILE *f = NULL;
+        XfopenFlags xflags = XFOPEN_UNLOCKED;
         int r;
 
         assert(filename);
         assert(ret_contents);
 
-        r = xfopenat(dir_fd, filename, "re", 0, &f);
-        if (r < 0) {
-                _cleanup_close_ int sk = -1;
-
-                /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
-                if (r != -ENXIO)
-                        return r;
-
-                /* If this is enabled, let's try to connect to it */
-                if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
-                        return -ENXIO;
-
-                /* Seeking is not supported on AF_UNIX sockets */
-                if (offset != UINT64_MAX)
-                        return -ENXIO;
+        if (FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET) && /* If this is enabled, let's try to connect to it */
+            offset == UINT64_MAX)                              /* Seeking is not supported on AF_UNIX sockets */
+                xflags |= XFOPEN_SOCKET;
 
-                sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
-                if (sk < 0)
-                        return -errno;
-
-                if (bind_name) {
-                        /* If the caller specified a socket name to bind to, do so before connecting. This is
-                         * useful to communicate some minor, short meta-information token from the client to
-                         * the server. */
-                        union sockaddr_union bsa;
-
-                        r = sockaddr_un_set_path(&bsa.un, bind_name);
-                        if (r < 0)
-                                return r;
-
-                        if (bind(sk, &bsa.sa, r) < 0)
-                                return -errno;
-                }
-
-                r = connect_unix_path(sk, dir_fd, filename);
-                if (IN_SET(r, -ENOTSOCK, -EINVAL)) /* propagate original error if this is not a socket after all */
-                        return -ENXIO;
-                if (r < 0)
-                        return r;
-
-                if (shutdown(sk, SHUT_WR) < 0)
-                        return -errno;
-
-                f = fdopen(sk, "r");
-                if (!f)
-                        return -errno;
-
-                TAKE_FD(sk);
-        }
-
-        (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+        r = xfopenat_full(dir_fd, filename, "re", 0, xflags, bind_name, &f);
+        if (r < 0)
+                return r;
 
         return read_full_stream_full(f, filename, offset, size, flags, ret_contents, ret_size);
 }
@@ -851,7 +798,6 @@ int executable_is_script(const char *path, char **interpreter) {
         *interpreter = ans;
         return 1;
 }
-#endif /* NM_IGNORED */
 
 /**
  * Retrieve one field from a file like /proc/self/status.  pattern
@@ -864,7 +810,6 @@ int executable_is_script(const char *path, char **interpreter) {
 int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) {
         _cleanup_free_ char *status = NULL;
         char *t, *f;
-        size_t len;
         int r;
 
         assert(terminator);
@@ -916,9 +861,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin
                         t--;
         }
 
-        len = strcspn(t, terminator);
-
-        f = strndup(t, len);
+        f = strdupcspn(t, terminator);
         if (!f)
                 return -ENOMEM;
 
@@ -927,8 +870,7 @@ int get_proc_field(const char *filename, const char *pattern, const char *termin
 }
 
 DIR *xopendirat(int fd, const char *name, int flags) {
-        int nfd;
-        DIR *d;
+        _cleanup_close_ int nfd = -EBADF;
 
         assert(!(flags & O_CREAT));
 
@@ -939,14 +881,9 @@ DIR *xopendirat(int fd, const char *name, int flags) {
         if (nfd < 0)
                 return NULL;
 
-        d = fdopendir(nfd);
-        if (!d) {
-                safe_close(nfd);
-                return NULL;
-        }
-
-        return d;
+        return take_fdopendir(&nfd);
 }
+#endif /* NM_IGNORED */
 
 int fopen_mode_to_flags(const char *mode) {
         const char *p;
@@ -994,38 +931,144 @@ int fopen_mode_to_flags(const char *mode) {
         return flags;
 }
 
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret) {
+static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
         FILE *f;
 
         /* A combination of fopen() with openat() */
 
-        if (dir_fd == AT_FDCWD && flags == 0) {
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(mode);
+        assert(ret);
+
+        if (dir_fd == AT_FDCWD && open_flags == 0)
                 f = fopen(path, mode);
-                if (!f)
-                        return -errno;
-        } else {
-                int fd, mode_flags;
+        else {
+                _cleanup_close_ int fd = -EBADF;
+                int mode_flags;
 
                 mode_flags = fopen_mode_to_flags(mode);
                 if (mode_flags < 0)
                         return mode_flags;
 
-                fd = openat(dir_fd, path, mode_flags | flags);
+                fd = openat(dir_fd, path, mode_flags | open_flags);
                 if (fd < 0)
                         return -errno;
 
-                f = fdopen(fd, mode);
-                if (!f) {
-                        safe_close(fd);
+                f = take_fdopen(&fd, mode);
+        }
+        if (!f)
+                return -errno;
+
+        *ret = f;
+        return 0;
+}
+
+static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_name, FILE **ret) {
+        _cleanup_close_ int sk = -EBADF;
+        FILE *f;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(ret);
+
+        sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+        if (sk < 0)
+                return -errno;
+
+        if (bind_name) {
+                /* If the caller specified a socket name to bind to, do so before connecting. This is
+                 * useful to communicate some minor, short meta-information token from the client to
+                 * the server. */
+                union sockaddr_union bsa;
+
+                r = sockaddr_un_set_path(&bsa.un, bind_name);
+                if (r < 0)
+                        return r;
+
+                if (bind(sk, &bsa.sa, r) < 0)
                         return -errno;
-                }
         }
 
+        r = connect_unix_path(sk, dir_fd, path);
+        if (r < 0)
+                return r;
+
+        if (shutdown(sk, SHUT_WR) < 0)
+                return -errno;
+
+        f = take_fdopen(&sk, "r");
+        if (!f)
+                return -errno;
+
+        *ret = f;
+        return 0;
+}
+
+int xfopenat_full(
+                int dir_fd,
+                const char *path,
+                const char *mode,
+                int open_flags,
+                XfopenFlags flags,
+                const char *bind_name,
+                FILE **ret) {
+
+        FILE *f = NULL;  /* avoid false maybe-uninitialized warning */
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(mode);
+        assert(ret);
+
+        r = xfopenat_regular(dir_fd, path, mode, open_flags, &f);
+        if (r == -ENXIO && FLAGS_SET(flags, XFOPEN_SOCKET)) {
+                /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
+                r = xfopenat_unix_socket(dir_fd, path, bind_name, &f);
+                if (IN_SET(r, -ENOTSOCK, -EINVAL))
+                        return -ENXIO; /* propagate original error if this is not a socket after all */
+        }
+        if (r < 0)
+                return r;
+
+        if (FLAGS_SET(flags, XFOPEN_UNLOCKED))
+                (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
+
         *ret = f;
         return 0;
 }
 
 #if 0 /* NM_IGNORED */
+int fdopen_independent(int fd, const char *mode, FILE **ret) {
+        _cleanup_close_ int copy_fd = -EBADF;
+        _cleanup_fclose_ FILE *f = NULL;
+        int mode_flags;
+
+        assert(fd >= 0);
+        assert(mode);
+        assert(ret);
+
+        /* A combination of fdopen() + fd_reopen(). i.e. reopens the inode the specified fd points to and
+         * returns a FILE* for it */
+
+        mode_flags = fopen_mode_to_flags(mode);
+        if (mode_flags < 0)
+                return mode_flags;
+
+        copy_fd = fd_reopen(fd, mode_flags);
+        if (copy_fd < 0)
+                return copy_fd;
+
+        f = take_fdopen(&copy_fd, mode);
+        if (!f)
+                return -errno;
+
+        *ret = TAKE_PTR(f);
+        return 0;
+}
+
 static int search_and_fopen_internal(
                 const char *path,
                 const char *mode,
@@ -1256,7 +1299,7 @@ typedef enum EndOfLineMarker {
 
 static EndOfLineMarker categorize_eol(char c, ReadLineFlags flags) {
 
-        if (!IN_SET(flags, READ_LINE_ONLY_NUL)) {
+        if (!FLAGS_SET(flags, READ_LINE_ONLY_NUL)) {
                 if (c == '\n')
                         return EOL_TEN;
                 if (c == '\r')
diff --git a/src/libnm-systemd-shared/src/basic/fileio.h b/src/libnm-systemd-shared/src/basic/fileio.h
index 9151d823..769bf394 100644
--- a/src/libnm-systemd-shared/src/basic/fileio.h
+++ b/src/libnm-systemd-shared/src/basic/fileio.h
@@ -43,7 +43,6 @@ typedef enum {
         READ_FULL_FILE_FAIL_WHEN_LARGER    = 1 << 5, /* fail loading if file is larger than specified size */
 } ReadFullFileFlags;
 
-int fopen_unlocked(const char *path, const char *options, FILE **ret);
 int fdopen_unlocked(int fd, const char *options, FILE **ret);
 int take_fdopen_unlocked(int *fd, const char *options, FILE **ret);
 FILE* take_fdopen(int *fd, const char *options);
@@ -55,15 +54,27 @@ int write_string_stream_ts(FILE *f, const char *line, WriteStringFileFlags flags
 static inline int write_string_stream(FILE *f, const char *line, WriteStringFileFlags flags) {
         return write_string_stream_ts(f, line, flags, NULL);
 }
-int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts);
+int write_string_file_ts_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts);
+static inline int write_string_file_ts(const char *fn, const char *line, WriteStringFileFlags flags, const struct timespec *ts) {
+        return write_string_file_ts_at(AT_FDCWD, fn, line, flags, ts);
+}
+static inline int write_string_file_at(int dir_fd, const char *fn, const char *line, WriteStringFileFlags flags) {
+        return write_string_file_ts_at(dir_fd, fn, line, flags, NULL);
+}
 static inline int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) {
         return write_string_file_ts(fn, line, flags, NULL);
 }
 
 int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
 
-int read_one_line_file(const char *filename, char **line);
+int read_one_line_file_at(int dir_fd, const char *filename, char **ret);
+static inline int read_one_line_file(const char *filename, char **ret) {
+        return read_one_line_file_at(AT_FDCWD, filename, ret);
+}
 int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size);
+static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) {
+        return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size);
+}
 static inline int read_full_file(const char *filename, char **ret_contents, size_t *ret_size) {
         return read_full_file_full(AT_FDCWD, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size);
 }
@@ -82,14 +93,41 @@ static inline int read_full_stream(FILE *f, char **ret_contents, size_t *ret_siz
         return read_full_stream_full(f, NULL, UINT64_MAX, SIZE_MAX, 0, ret_contents, ret_size);
 }
 
-int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
+int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl);
+static inline int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
+        return verify_file_at(AT_FDCWD, fn, blob, accept_extra_nl);
+}
 
 int executable_is_script(const char *path, char **interpreter);
 
 int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field);
 
 DIR *xopendirat(int dirfd, const char *name, int flags);
-int xfopenat(int dir_fd, const char *path, const char *mode, int flags, FILE **ret);
+
+typedef enum XfopenFlags {
+        XFOPEN_UNLOCKED = 1 << 0, /* call __fsetlocking(FSETLOCKING_BYCALLER) after opened */
+        XFOPEN_SOCKET   = 1 << 1, /* also try to open unix socket */
+} XfopenFlags;
+
+int xfopenat_full(
+                int dir_fd,
+                const char *path,
+                const char *mode,
+                int open_flags,
+                XfopenFlags flags,
+                const char *bind_name,
+                FILE **ret);
+static inline int xfopenat(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+        return xfopenat_full(dir_fd, path, mode, open_flags, 0, NULL, ret);
+}
+static inline int fopen_unlocked_at(int dir_fd, const char *path, const char *mode, int open_flags, FILE **ret) {
+        return xfopenat_full(dir_fd, path, mode, open_flags, XFOPEN_UNLOCKED, NULL, ret);
+}
+static inline int fopen_unlocked(const char *path, const char *mode, FILE **ret) {
+        return fopen_unlocked_at(AT_FDCWD, path, mode, 0, ret);
+}
+
+int fdopen_independent(int fd, const char *mode, FILE **ret);
 
 int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **ret, char **ret_path);
 int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **ret, char **ret_path);
diff --git a/src/libnm-systemd-shared/src/basic/fs-util.c b/src/libnm-systemd-shared/src/basic/fs-util.c
index 23f8d403..32fd849d 100644
--- a/src/libnm-systemd-shared/src/basic/fs-util.c
+++ b/src/libnm-systemd-shared/src/basic/fs-util.c
@@ -5,6 +5,7 @@
 #include <errno.h>
 #include <stddef.h>
 #include <stdlib.h>
+#include <sys/file.h>
 #include <linux/falloc.h>
 #include <linux/magic.h>
 #include <unistd.h>
@@ -15,6 +16,8 @@
 #include "fileio.h"
 #include "fs-util.h"
 #include "hostname-util.h"
+#include "label.h"
+#include "lock-util.h"
 #include "log.h"
 #include "macro.h"
 #include "missing_fcntl.h"
@@ -34,12 +37,6 @@
 #include "tmpfile-util.h"
 #include "umask-util.h"
 #include "user-util.h"
-#include "util.h"
-
-int unlink_noerrno(const char *path) {
-        PROTECT_ERRNO;
-        return RET_NERRNO(unlink(path));
-}
 
 #if 0 /* NM_IGNORED */
 int rmdir_parents(const char *path, const char *stop) {
@@ -180,37 +177,41 @@ int readlink_value(const char *p, char **ret) {
         return 0;
 }
 
-int readlink_and_make_absolute(const char *p, char **r) {
+int readlink_and_make_absolute(const char *p, char **ret) {
         _cleanup_free_ char *target = NULL;
-        char *k;
-        int j;
+        int r;
 
         assert(p);
-        assert(r);
-
-        j = readlink_malloc(p, &target);
-        if (j < 0)
-                return j;
+        assert(ret);
 
-        k = file_in_same_dir(p, target);
-        if (!k)
-                return -ENOMEM;
+        r = readlink_malloc(p, &target);
+        if (r < 0)
+                return r;
 
-        *r = k;
-        return 0;
+        return file_in_same_dir(p, target, ret);
 }
 
-int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
-        _cleanup_close_ int fd = -1;
+int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        _cleanup_close_ int fd = -EBADF;
 
-        assert(path);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
 
-        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW); /* Let's acquire an O_PATH fd, as precaution to change
-                                                       * mode/owner on the same file */
-        if (fd < 0)
-                return -errno;
+        if (path) {
+                /* Let's acquire an O_PATH fd, as precaution to change mode/owner on the same file */
+                fd = openat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+                if (fd < 0)
+                        return -errno;
+                dir_fd = fd;
 
-        return fchmod_and_chown(fd, mode, uid, gid);
+        } else if (dir_fd == AT_FDCWD) {
+                /* Let's acquire an O_PATH fd of the current directory */
+                fd = openat(dir_fd, ".", O_PATH|O_CLOEXEC|O_NOFOLLOW|O_DIRECTORY);
+                if (fd < 0)
+                        return -errno;
+                dir_fd = fd;
+        }
+
+        return fchmod_and_chown(dir_fd, mode, uid, gid);
 }
 
 int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid) {
@@ -308,7 +309,7 @@ int fchmod_opath(int fd, mode_t m) {
 }
 
 int futimens_opath(int fd, const struct timespec ts[2]) {
-        /* Similar to fchmod_path() but for futimens() */
+        /* Similar to fchmod_opath() but for futimens() */
 
         if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, 0) < 0) {
                 if (errno != ENOENT)
@@ -356,7 +357,7 @@ int fd_warn_permissions(const char *path, int fd) {
 }
 
 int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         int r, ret;
 
         assert(path);
@@ -680,11 +681,11 @@ void unlink_tempfilep(char (*p)[]) {
          * successfully created. We ignore both the rare case where the
          * original suffix is used and unlink failures. */
         if (!endswith(*p, ".XXXXXX"))
-                (void) unlink_noerrno(*p);
+                (void) unlink(*p);
 }
 
 int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
-        _cleanup_close_ int truncate_fd = -1;
+        _cleanup_close_ int truncate_fd = -EBADF;
         struct stat st;
         off_t l, bs;
 
@@ -778,7 +779,7 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
          * punch-hole/truncate this to release the disk space. */
 
         bs = MAX(st.st_blksize, 512);
-        l = DIV_ROUND_UP(st.st_size, bs) * bs; /* Round up to next block size */
+        l = ROUND_UP(st.st_size, bs); /* Round up to next block size */
 
         if (fallocate(truncate_fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, 0, l) >= 0)
                 return 0; /* Successfully punched a hole! 😊 */
@@ -792,12 +793,23 @@ int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) {
         return 0;
 }
 
-int open_parent(const char *path, int flags, mode_t mode) {
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode) {
         _cleanup_free_ char *parent = NULL;
         int r;
 
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
         r = path_extract_directory(path, &parent);
-        if (r < 0)
+        if (r == -EDESTADDRREQ) {
+                parent = strdup(".");
+                if (!parent)
+                        return -ENOMEM;
+        } else if (r == -EADDRNOTAVAIL) {
+                parent = strdup(path);
+                if (!parent)
+                        return -ENOMEM;
+        } else if (r < 0)
                 return r;
 
         /* Let's insist on O_DIRECTORY since the parent of a file or directory is a directory. Except if we open an
@@ -808,7 +820,7 @@ int open_parent(const char *path, int flags, mode_t mode) {
         else if (!FLAGS_SET(flags, O_TMPFILE))
                 flags |= O_DIRECTORY|O_RDONLY;
 
-        return RET_NERRNO(open(parent, flags, mode));
+        return RET_NERRNO(openat(dir_fd, parent, flags, mode));
 }
 #endif /* NM_IGNORED */
 
@@ -816,14 +828,14 @@ int conservative_renameat(
                 int olddirfd, const char *oldpath,
                 int newdirfd, const char *newpath) {
 
-        _cleanup_close_ int old_fd = -1, new_fd = -1;
+        _cleanup_close_ int old_fd = -EBADF, new_fd = -EBADF;
         struct stat old_stat, new_stat;
 
-        /* Renames the old path to thew new path, much like renameat() — except if both are regular files and
+        /* Renames the old path to the new path, much like renameat() — except if both are regular files and
          * have the exact same contents and basic file attributes already. In that case remove the new file
          * instead. This call is useful for reducing inotify wakeups on files that are updated but don't
          * actually change. This function is written in a style that we rather rename too often than suppress
-         * too much. i.e. whenever we are in doubt we rather rename than fail. After all reducing inotify
+         * too much. I.e. whenever we are in doubt, we rather rename than fail. After all reducing inotify
          * events is an optimization only, not more. */
 
         old_fd = openat(olddirfd, oldpath, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_NOFOLLOW);
@@ -912,7 +924,7 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size) {
 
         /* On EINTR try a couple of times more, but protect against busy looping
          * (not more than 16 times per 10s) */
-        rl = (RateLimit) { 10 * USEC_PER_SEC, 16 };
+        rl = (const RateLimit) { 10 * USEC_PER_SEC, 16 };
         while (ratelimit_below(&rl)) {
                 r = posix_fallocate(fd, offset, size);
                 if (r != EINTR)
@@ -998,9 +1010,8 @@ int parse_cifs_service(
 }
 
 int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
-        _cleanup_close_ int fd = -1, parent_fd = -1;
-        _cleanup_free_ char *fname = NULL;
-        bool made;
+        _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF;
+        _cleanup_free_ char *fname = NULL, *parent = NULL;
         int r;
 
         /* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
@@ -1015,19 +1026,13 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
         /* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
          * flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
 
-        if (isempty(path))
-                return -EINVAL;
-
-        if (!filename_is_valid(path)) {
-                _cleanup_free_ char *parent = NULL;
-
-                /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
-                 * that we can pin it, and operate below it. */
-
-                r = path_extract_directory(path, &parent);
-                if (r < 0)
+        /* If this is not a valid filename, it's a path. Let's open the parent directory then, so
+         * that we can pin it, and operate below it. */
+        r = path_extract_directory(path, &parent);
+        if (r < 0) {
+                if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL))
                         return r;
-
+        } else {
                 r = path_extract_filename(path, &fname);
                 if (r < 0)
                         return r;
@@ -1040,33 +1045,11 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
                 path = fname;
         }
 
-        r = RET_NERRNO(mkdirat(dirfd, path, mode));
-        if (r == -EEXIST) {
-                if (FLAGS_SET(flags, O_EXCL))
-                        return -EEXIST;
-
-                made = false;
-        } else if (r < 0)
-                return r;
-        else
-                made = true;
-
-        fd = RET_NERRNO(openat(dirfd, path, (flags & ~O_EXCL)|O_DIRECTORY|O_NOFOLLOW));
-        if (fd < 0) {
-                if (fd == -ENOENT)  /* We got ENOENT? then someone else immediately removed it after we
-                                     * created it. In that case let's return immediately without unlinking
-                                     * anything, because there simply isn't anything to unlink anymore. */
-                        return -ENOENT;
-                if (fd == -ELOOP)   /* is a symlink? exists already → created by someone else, don't unlink */
-                        return -EEXIST;
-                if (fd == -ENOTDIR) /* not a directory? exists already → created by someone else, don't unlink */
-                        return -EEXIST;
-
-                if (made)
-                        (void) unlinkat(dirfd, path, AT_REMOVEDIR);
-
+        fd = xopenat(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode);
+        if (IN_SET(fd, -ELOOP, -ENOTDIR))
+                return -EEXIST;
+        if (fd < 0)
                 return fd;
-        }
 
         return TAKE_FD(fd);
 }
@@ -1117,3 +1100,120 @@ int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, b
                         return -EEXIST;
         }
 }
+
+int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode) {
+        _cleanup_close_ int fd = -EBADF;
+        bool made = false;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+
+        if (isempty(path)) {
+                assert(!FLAGS_SET(open_flags, O_CREAT|O_EXCL));
+                return fd_reopen(dir_fd, open_flags & ~O_NOFOLLOW);
+        }
+
+        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
+                r = label_ops_pre(dir_fd, path, FLAGS_SET(open_flags, O_DIRECTORY) ? S_IFDIR : S_IFREG);
+                if (r < 0)
+                        return r;
+        }
+
+        if (FLAGS_SET(open_flags, O_DIRECTORY|O_CREAT)) {
+                r = RET_NERRNO(mkdirat(dir_fd, path, mode));
+                if (r == -EEXIST) {
+                        if (FLAGS_SET(open_flags, O_EXCL))
+                                return -EEXIST;
+
+                        made = false;
+                } else if (r < 0)
+                        return r;
+                else
+                        made = true;
+
+                if (FLAGS_SET(xopen_flags, XO_LABEL)) {
+                        r = label_ops_post(dir_fd, path);
+                        if (r < 0)
+                                return r;
+                }
+
+                open_flags &= ~(O_EXCL|O_CREAT);
+                xopen_flags &= ~XO_LABEL;
+        }
+
+        fd = RET_NERRNO(openat(dir_fd, path, open_flags, mode));
+        if (fd < 0) {
+                if (IN_SET(fd,
+                           /* We got ENOENT? then someone else immediately removed it after we
+                           * created it. In that case let's return immediately without unlinking
+                           * anything, because there simply isn't anything to unlink anymore. */
+                           -ENOENT,
+                           /* is a symlink? exists already → created by someone else, don't unlink */
+                           -ELOOP,
+                           /* not a directory? exists already → created by someone else, don't unlink */
+                           -ENOTDIR))
+                        return fd;
+
+                if (made)
+                        (void) unlinkat(dir_fd, path, AT_REMOVEDIR);
+
+                return fd;
+        }
+
+        if (FLAGS_SET(open_flags, O_CREAT) && FLAGS_SET(xopen_flags, XO_LABEL)) {
+                r = label_ops_post(dir_fd, path);
+                if (r < 0)
+                        return r;
+        }
+
+        return TAKE_FD(fd);
+}
+
+#if 0 /* NM_IGNORED */
+int xopenat_lock(
+                int dir_fd,
+                const char *path,
+                int open_flags,
+                XOpenFlags xopen_flags,
+                mode_t mode,
+                LockType locktype,
+                int operation) {
+
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+        assert(IN_SET(operation & ~LOCK_NB, LOCK_EX, LOCK_SH));
+
+        /* POSIX/UNPOSIX locks don't work on directories (errno is set to -EBADF so let's return early with
+         * the same error here). */
+        if (FLAGS_SET(open_flags, O_DIRECTORY) && locktype != LOCK_BSD)
+                return -EBADF;
+
+        for (;;) {
+                struct stat st;
+
+                fd = xopenat(dir_fd, path, open_flags, xopen_flags, mode);
+                if (fd < 0)
+                        return fd;
+
+                r = lock_generic(fd, locktype, operation);
+                if (r < 0)
+                        return r;
+
+                /* If we acquired the lock, let's check if the file/directory still exists in the file
+                 * system. If not, then the previous exclusive owner removed it and then closed it. In such a
+                 * case our acquired lock is worthless, hence try again. */
+
+                if (fstat(fd, &st) < 0)
+                        return -errno;
+                if (st.st_nlink > 0)
+                        break;
+
+                fd = safe_close(fd);
+        }
+
+        return TAKE_FD(fd);
+}
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/fs-util.h b/src/libnm-systemd-shared/src/basic/fs-util.h
index c4dffc48..a19836d1 100644
--- a/src/libnm-systemd-shared/src/basic/fs-util.h
+++ b/src/libnm-systemd-shared/src/basic/fs-util.h
@@ -12,6 +12,7 @@
 
 #include "alloc-util.h"
 #include "errno-util.h"
+#include "lock-util.h"
 #include "time-util.h"
 #include "user-util.h"
 
@@ -22,8 +23,6 @@
 #define PTR_TO_MODE(p) ((mode_t) ((uintptr_t) (p)-1))
 #define MODE_TO_PTR(u) ((void *) ((uintptr_t) (u)+1))
 
-int unlink_noerrno(const char *path);
-
 int rmdir_parents(const char *path, const char *stop);
 
 int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
@@ -33,7 +32,10 @@ int readlink_malloc(const char *p, char **r);
 int readlink_value(const char *p, char **ret);
 int readlink_and_make_absolute(const char *p, char **r);
 
-int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid);
+int chmod_and_chown_at(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid);
+static inline int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+        return chmod_and_chown_at(AT_FDCWD, path, mode, uid, gid);
+}
 int fchmod_and_chown_with_fallback(int fd, const char *path, mode_t mode, uid_t uid, gid_t gid);
 static inline int fchmod_and_chown(int fd, mode_t mode, uid_t uid, gid_t gid) {
         return fchmod_and_chown_with_fallback(fd, NULL, mode, uid, gid); /* no fallback */
@@ -96,7 +98,7 @@ static inline char* unlink_and_free(char *p) {
         if (!p)
                 return NULL;
 
-        (void) unlink_noerrno(p);
+        (void) unlink(p);
         return mfree(p);
 }
 DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free);
@@ -112,7 +114,10 @@ typedef enum UnlinkDeallocateFlags {
 
 int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags);
 
-int open_parent(const char *path, int flags, mode_t mode);
+int open_parent_at(int dir_fd, const char *path, int flags, mode_t mode);
+static inline int open_parent(const char *path, int flags, mode_t mode) {
+        return open_parent_at(AT_FDCWD, path, flags, mode);
+}
 
 int conservative_renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath);
 static inline int conservative_rename(const char *oldpath, const char *newpath) {
@@ -126,3 +131,11 @@ int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char
 int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode);
 
 int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created);
+
+typedef enum XOpenFlags {
+        XO_LABEL = 1 << 0,
+} XOpenFlags;
+
+int xopenat(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode);
+
+int xopenat_lock(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode, LockType locktype, int operation);
diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.c b/src/libnm-systemd-shared/src/basic/glyph-util.c
index 5289cffe..1ea8a645 100644
--- a/src/libnm-systemd-shared/src/basic/glyph-util.c
+++ b/src/libnm-systemd-shared/src/basic/glyph-util.c
@@ -11,15 +11,15 @@ bool emoji_enabled(void) {
         static int cached_emoji_enabled = -1;
 
         if (cached_emoji_enabled < 0) {
-                int val;
-
-                val = getenv_bool("SYSTEMD_EMOJI");
-                if (val < 0)
-                        cached_emoji_enabled =
-                                is_locale_utf8() &&
-                                !STRPTR_IN_SET(getenv("TERM"), "dumb", "linux");
-                else
-                        cached_emoji_enabled = val;
+                int val = getenv_bool("SYSTEMD_EMOJI");
+                if (val >= 0)
+                        return (cached_emoji_enabled = val);
+
+                const char *term = getenv("TERM");
+                if (!term || STR_IN_SET(term, "dumb", "linux"))
+                        return (cached_emoji_enabled = false);
+
+                cached_emoji_enabled = is_locale_utf8();
         }
 
         return cached_emoji_enabled;
@@ -73,6 +73,7 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_RECYCLING]               = "~",
                         [SPECIAL_GLYPH_DOWNLOAD]                = "\\",
                         [SPECIAL_GLYPH_SPARKLES]                = "*",
+                        [SPECIAL_GLYPH_WARNING_SIGN]            = "!",
                 },
 
                 /* UTF-8 */
@@ -126,10 +127,11 @@ const char *special_glyph(SpecialGlyph code) {
                         /* This emoji is a single character cell glyph in Unicode, and two in ASCII */
                         [SPECIAL_GLYPH_TOUCH]                   = u8"👆",       /* actually called: BACKHAND INDEX POINTING UP */
 
-                        /* These three emojis are single character cell glyphs in Unicode and also in ASCII. */
+                        /* These four emojis are single character cell glyphs in Unicode and also in ASCII. */
                         [SPECIAL_GLYPH_RECYCLING]               = u8"♻️",        /* actually called: UNIVERSAL RECYCLNG SYMBOL */
                         [SPECIAL_GLYPH_DOWNLOAD]                = u8"⤵️",        /* actually called: RIGHT ARROW CURVING DOWN */
                         [SPECIAL_GLYPH_SPARKLES]                = u8"✨",
+                        [SPECIAL_GLYPH_WARNING_SIGN]            = u8"⚠️",
                 },
         };
 
diff --git a/src/libnm-systemd-shared/src/basic/glyph-util.h b/src/libnm-systemd-shared/src/basic/glyph-util.h
index 621d7a85..b6463962 100644
--- a/src/libnm-systemd-shared/src/basic/glyph-util.h
+++ b/src/libnm-systemd-shared/src/basic/glyph-util.h
@@ -44,6 +44,7 @@ typedef enum SpecialGlyph {
         SPECIAL_GLYPH_RECYCLING,
         SPECIAL_GLYPH_DOWNLOAD,
         SPECIAL_GLYPH_SPARKLES,
+        SPECIAL_GLYPH_WARNING_SIGN,
         _SPECIAL_GLYPH_MAX,
         _SPECIAL_GLYPH_INVALID = -EINVAL,
 } SpecialGlyph;
diff --git a/src/libnm-systemd-shared/src/basic/hash-funcs.h b/src/libnm-systemd-shared/src/basic/hash-funcs.h
index c14302ec..be642892 100644
--- a/src/libnm-systemd-shared/src/basic/hash-funcs.h
+++ b/src/libnm-systemd-shared/src/basic/hash-funcs.h
@@ -102,7 +102,7 @@ extern const struct hash_ops uint64_hash_ops;
 /* On some archs dev_t is 32bit, and on others 64bit. And sometimes it's 64bit on 32bit archs, and sometimes 32bit on
  * 64bit archs. Yuck! */
 #if SIZEOF_DEV_T != 8
-void devt_hash_func(const dev_t *p, struct siphash *state) _pure_;
+void devt_hash_func(const dev_t *p, struct siphash *state);
 #else
 #define devt_hash_func uint64_hash_func
 #endif
diff --git a/src/libnm-systemd-shared/src/basic/hashmap.c b/src/libnm-systemd-shared/src/basic/hashmap.c
index d0b950e0..356200cf 100644
--- a/src/libnm-systemd-shared/src/basic/hashmap.c
+++ b/src/libnm-systemd-shared/src/basic/hashmap.c
@@ -7,10 +7,14 @@
 #include <pthread.h>
 #include <stdint.h>
 #include <stdlib.h>
+#if HAVE_VALGRIND_VALGRIND_H
+#  include <valgrind/valgrind.h>
+#endif
 
 #include "alloc-util.h"
 #include "fileio.h"
 #include "hashmap.h"
+#include "logarithm.h"
 #include "macro.h"
 #include "memory-util.h"
 #include "mempool.h"
@@ -275,29 +279,34 @@ static _used_ const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX
         },
 };
 
-#if VALGRIND
-_destructor_ static void cleanup_pools(void) {
-        _cleanup_free_ char *t = NULL;
+#if 0 /* NM_IGNORED */
+void hashmap_trim_pools(void) {
         int r;
 
-        /* Be nice to valgrind */
+        /* The pool is only allocated by the main thread, but the memory can be passed to other
+         * threads. Let's clean up if we are the main thread and no other threads are live. */
 
-        /* The pool is only allocated by the main thread, but the memory can
-         * be passed to other threads. Let's clean up if we are the main thread
-         * and no other threads are live. */
-        /* We build our own is_main_thread() here, which doesn't use C11
-         * TLS based caching of the result. That's because valgrind apparently
-         * doesn't like malloc() (which C11 TLS internally uses) to be called
-         * from a GCC destructors. */
+        /* We build our own is_main_thread() here, which doesn't use C11 TLS based caching of the
+         * result. That's because valgrind apparently doesn't like TLS to be used from a GCC destructor. */
         if (getpid() != gettid())
-                return;
+                return (void) log_debug("Not cleaning up memory pools, not in main thread.");
 
-        r = get_proc_field("/proc/self/status", "Threads", WHITESPACE, &t);
-        if (r < 0 || !streq(t, "1"))
-                return;
+        r = get_process_threads(0);
+        if (r < 0)
+                return (void) log_debug_errno(r, "Failed to determine number of threads, not cleaning up memory pools: %m");
+        if (r != 1)
+                return (void) log_debug("Not cleaning up memory pools, running in multi-threaded process.");
+
+        mempool_trim(&hashmap_pool);
+        mempool_trim(&ordered_hashmap_pool);
+}
+#endif /* NM_IGNORED */
 
-        mempool_drop(&hashmap_pool);
-        mempool_drop(&ordered_hashmap_pool);
+#if HAVE_VALGRIND_VALGRIND_H
+_destructor_ static void cleanup_pools(void) {
+        /* Be nice to valgrind */
+        if (RUNNING_ON_VALGRIND)
+                hashmap_trim_pools();
 }
 #endif
 
@@ -374,8 +383,9 @@ static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) {
 }
 
 static struct hashmap_base_entry* bucket_at(HashmapBase *h, unsigned idx) {
-        return (struct hashmap_base_entry*) (void *)
-                ((uint8_t*) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size);
+        return CAST_ALIGN_PTR(
+                        struct hashmap_base_entry,
+                        (uint8_t *) storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size);
 }
 
 static struct plain_hashmap_entry* plain_bucket_at(Hashmap *h, unsigned idx) {
@@ -774,7 +784,7 @@ static struct HashmapBase* hashmap_base_new(const struct hash_ops *hash_ops, enu
         HashmapBase *h;
         const struct hashmap_type_info *hi = &hashmap_type_info[type];
 
-        bool use_pool = mempool_enabled && mempool_enabled();
+        bool use_pool = mempool_enabled && mempool_enabled();  /* mempool_enabled is a weak symbol */
 
         h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
         if (!h)
@@ -1753,7 +1763,7 @@ HashmapBase* _hashmap_copy(HashmapBase *h  HASHMAP_DEBUG_PARAMS) {
         }
 
         if (r < 0)
-                return _hashmap_free(copy, false, false);
+                return _hashmap_free(copy, NULL, NULL);
 
         return copy;
 }
diff --git a/src/libnm-systemd-shared/src/basic/hashmap.h b/src/libnm-systemd-shared/src/basic/hashmap.h
index 91b3fe86..68d9b81c 100644
--- a/src/libnm-systemd-shared/src/basic/hashmap.h
+++ b/src/libnm-systemd-shared/src/basic/hashmap.h
@@ -7,7 +7,6 @@
 
 #include "hash-funcs.h"
 #include "macro.h"
-#include "util.h"
 
 /*
  * A hash table implementation. As a minor optimization a NULL hashmap object
@@ -444,3 +443,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free);
 DEFINE_TRIVIAL_CLEANUP_FUNC(IteratedCache*, iterated_cache_free);
 
 #define _cleanup_iterated_cache_free_ _cleanup_(iterated_cache_freep)
+
+void hashmap_trim_pools(void);
diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.c b/src/libnm-systemd-shared/src/basic/hexdecoct.c
index 570da688..d41d2ea0 100644
--- a/src/libnm-systemd-shared/src/basic/hexdecoct.c
+++ b/src/libnm-systemd-shared/src/basic/hexdecoct.c
@@ -61,11 +61,13 @@ char *hexmem(const void *p, size_t l) {
         const uint8_t *x;
         char *r, *z;
 
+        assert(p || l == 0);
+
         z = r = new(char, l * 2 + 1);
         if (!r)
                 return NULL;
 
-        for (x = p; x < (const uint8_t*) p + l; x++) {
+        for (x = p; x && x < (const uint8_t*) p + l; x++) {
                 *(z++) = hexchar(*x >> 4);
                 *(z++) = hexchar(*x & 15);
         }
@@ -110,12 +112,17 @@ static int unhex_next(const char **p, size_t *l) {
         return r;
 }
 
-int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_len) {
+int unhexmem_full(
+                const char *p,
+                size_t l,
+                bool secure,
+                void **ret,
+                size_t *ret_len) {
+
         _cleanup_free_ uint8_t *buf = NULL;
         size_t buf_size;
         const char *x;
         uint8_t *z;
-        int r;
 
         assert(p || l == 0);
 
@@ -128,22 +135,20 @@ int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_
         if (!buf)
                 return -ENOMEM;
 
+        CLEANUP_ERASE_PTR(secure ? &buf : NULL, buf_size);
+
         for (x = p, z = buf;;) {
                 int a, b;
 
                 a = unhex_next(&x, &l);
                 if (a == -EPIPE) /* End of string */
                         break;
-                if (a < 0) {
-                        r = a;
-                        goto on_failure;
-                }
+                if (a < 0)
+                        return a;
 
                 b = unhex_next(&x, &l);
-                if (b < 0) {
-                        r = b;
-                        goto on_failure;
-                }
+                if (b < 0)
+                        return b;
 
                 *(z++) = (uint8_t) a << 4 | (uint8_t) b;
         }
@@ -156,12 +161,6 @@ int unhexmem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_
                 *ret = TAKE_PTR(buf);
 
         return 0;
-
-on_failure:
-        if (secure)
-                explicit_bzero_safe(buf, buf_size);
-
-        return r;
 }
 
 #if 0 /* NM_IGNORED */
@@ -591,121 +590,136 @@ ssize_t base64mem_full(
                 const void *p,
                 size_t l,
                 size_t line_break,
-                char **out) {
+                char **ret) {
 
         const uint8_t *x;
-        char *r, *z;
+        char *b, *z;
         size_t m;
 
         assert(p || l == 0);
-        assert(out);
         assert(line_break > 0);
+        assert(ret);
 
         /* three input bytes makes four output bytes, padding is added so we must round up */
         m = 4 * (l + 2) / 3 + 1;
-
         if (line_break != SIZE_MAX)
                 m += m / line_break;
 
-        z = r = malloc(m);
-        if (!r)
+        z = b = malloc(m);
+        if (!b)
                 return -ENOMEM;
 
-        for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
+        for (x = p; x && x < (const uint8_t*) p + (l / 3) * 3; x += 3) {
                 /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char(x[0] >> 2);                    /* 00XXXXXX */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4);  /* 00XXYYYY */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char(x[2] & 63);                    /* 00ZZZZZZ */
         }
 
         switch (l % 3) {
         case 2:
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char(x[0] >> 2);                   /* 00XXXXXX */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[1] & 15) << 2);            /* 00YYYY00 */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = '=';
-
                 break;
+
         case 1:
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char(x[0] >> 2);        /* 00XXXXXX */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = base64char((x[0] & 3) << 4);  /* 00XX0000 */
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = '=';
-                maybe_line_break(&z, r, line_break);
+                maybe_line_break(&z, b, line_break);
                 *(z++) = '=';
-
                 break;
         }
 
         *z = 0;
-        *out = r;
-        assert(z >= r); /* Let static analyzers know that the answer is non-negative. */
-        return z - r;
+        *ret = b;
+
+        assert(z >= b); /* Let static analyzers know that the answer is non-negative. */
+        return z - b;
 }
 
-static int base64_append_width(
-                char **prefix, int plen,
-                char sep, int indent,
-                const void *p, size_t l,
-                int width) {
+static ssize_t base64_append_width(
+                char **prefix,
+                size_t plen,
+                char sep,
+                size_t indent,
+                const void *p,
+                size_t l,
+                size_t width) {
 
         _cleanup_free_ char *x = NULL;
         char *t, *s;
-        ssize_t len, avail, line, lines;
+        size_t lines;
+        ssize_t len;
+
+        assert(prefix);
+        assert(*prefix || plen == 0);
+        assert(p || l == 0);
 
         len = base64mem(p, l, &x);
-        if (len <= 0)
+        if (len < 0)
                 return len;
+        if (len == 0)
+                return plen;
 
         lines = DIV_ROUND_UP(len, width);
 
-        if ((size_t) plen >= SSIZE_MAX - 1 - 1 ||
+        if (plen >= SSIZE_MAX - 1 - 1 ||
             lines > (SSIZE_MAX - plen - 1 - 1) / (indent + width + 1))
                 return -ENOMEM;
 
-        t = realloc(*prefix, (ssize_t) plen + 1 + 1 + (indent + width + 1) * lines);
+        t = realloc(*prefix, plen + 1 + 1 + (indent + width + 1) * lines);
         if (!t)
                 return -ENOMEM;
 
-        t[plen] = sep;
+        s = t + plen;
+        for (size_t line = 0; line < lines; line++) {
+                size_t act = MIN(width, (size_t) len);
 
-        for (line = 0, s = t + plen + 1, avail = len; line < lines; line++) {
-                int act = MIN(width, avail);
+                if (line > 0)
+                        sep = '\n';
 
-                if (line > 0 || sep == '\n') {
-                        memset(s, ' ', indent);
-                        s += indent;
+                if (s > t) {
+                        *s++ = sep;
+                        if (sep == '\n')
+                                s = mempset(s, ' ', indent);
                 }
 
                 s = mempcpy(s, x + width * line, act);
-                *(s++) = line < lines - 1 ? '\n' : '\0';
-                avail -= act;
+                len -= act;
         }
-        assert(avail == 0);
+        assert(len == 0);
 
+        *s = '\0';
         *prefix = t;
-        return 0;
+        return s - t;
 }
 
-int base64_append(
-                char **prefix, int plen,
-                const void *p, size_t l,
-                int indent, int width) {
+ssize_t base64_append(
+                char **prefix,
+                size_t plen,
+                const void *p,
+                size_t l,
+                size_t indent,
+                size_t width) {
 
         if (plen > width / 2 || plen + indent > width)
                 /* leave indent on the left, keep last column free */
-                return base64_append_width(prefix, plen, '\n', indent, p, l, width - indent - 1);
+                return base64_append_width(prefix, plen, '\n', indent, p, l, width - indent);
         else
                 /* leave plen on the left, keep last column free */
                 return base64_append_width(prefix, plen, ' ', plen + 1, p, l, width - plen - 1);
@@ -754,12 +768,17 @@ static int unbase64_next(const char **p, size_t *l) {
         return ret;
 }
 
-int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *ret_size) {
+int unbase64mem_full(
+                const char *p,
+                size_t l,
+                bool secure,
+                void **ret,
+                size_t *ret_size) {
+
         _cleanup_free_ uint8_t *buf = NULL;
         const char *x;
         uint8_t *z;
         size_t len;
-        int r;
 
         assert(p || l == 0);
 
@@ -774,60 +793,44 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
         if (!buf)
                 return -ENOMEM;
 
+        CLEANUP_ERASE_PTR(secure ? &buf : NULL, len);
+
         for (x = p, z = buf;;) {
                 int a, b, c, d; /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */
 
                 a = unbase64_next(&x, &l);
                 if (a == -EPIPE) /* End of string */
                         break;
-                if (a < 0) {
-                        r = a;
-                        goto on_failure;
-                }
-                if (a == INT_MAX) { /* Padding is not allowed at the beginning of a 4ch block */
-                        r = -EINVAL;
-                        goto on_failure;
-                }
+                if (a < 0)
+                        return a;
+                if (a == INT_MAX) /* Padding is not allowed at the beginning of a 4ch block */
+                        return -EINVAL;
 
                 b = unbase64_next(&x, &l);
-                if (b < 0) {
-                        r = b;
-                        goto on_failure;
-                }
-                if (b == INT_MAX) { /* Padding is not allowed at the second character of a 4ch block either */
-                        r = -EINVAL;
-                        goto on_failure;
-                }
+                if (b < 0)
+                        return b;
+                if (b == INT_MAX) /* Padding is not allowed at the second character of a 4ch block either */
+                        return -EINVAL;
 
                 c = unbase64_next(&x, &l);
-                if (c < 0) {
-                        r = c;
-                        goto on_failure;
-                }
+                if (c < 0)
+                        return c;
 
                 d = unbase64_next(&x, &l);
-                if (d < 0) {
-                        r = d;
-                        goto on_failure;
-                }
+                if (d < 0)
+                        return d;
 
                 if (c == INT_MAX) { /* Padding at the third character */
 
-                        if (d != INT_MAX) { /* If the third character is padding, the fourth must be too */
-                                r = -EINVAL;
-                                goto on_failure;
-                        }
+                        if (d != INT_MAX) /* If the third character is padding, the fourth must be too */
+                                return -EINVAL;
 
                         /* b == 00YY0000 */
-                        if (b & 15) {
-                                r = -EINVAL;
-                                goto on_failure;
-                        }
+                        if (b & 15)
+                                return -EINVAL;
 
-                        if (l > 0) { /* Trailing rubbish? */
-                                r = -ENAMETOOLONG;
-                                goto on_failure;
-                        }
+                        if (l > 0) /* Trailing rubbish? */
+                                return -ENAMETOOLONG;
 
                         *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */
                         break;
@@ -835,15 +838,11 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
 
                 if (d == INT_MAX) {
                         /* c == 00ZZZZ00 */
-                        if (c & 3) {
-                                r = -EINVAL;
-                                goto on_failure;
-                        }
+                        if (c & 3)
+                                return -EINVAL;
 
-                        if (l > 0) { /* Trailing rubbish? */
-                                r = -ENAMETOOLONG;
-                                goto on_failure;
-                        }
+                        if (l > 0) /* Trailing rubbish? */
+                                return -ENAMETOOLONG;
 
                         *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */
                         *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */
@@ -857,18 +856,14 @@ int unbase64mem_full(const char *p, size_t l, bool secure, void **ret, size_t *r
 
         *z = 0;
 
+        assert((size_t) (z - buf) <= len);
+
         if (ret_size)
                 *ret_size = (size_t) (z - buf);
         if (ret)
                 *ret = TAKE_PTR(buf);
 
         return 0;
-
-on_failure:
-        if (secure)
-                explicit_bzero_safe(buf, len);
-
-        return r;
 }
 
 #if 0 /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/hexdecoct.h b/src/libnm-systemd-shared/src/basic/hexdecoct.h
index 5218f786..319b21a1 100644
--- a/src/libnm-systemd-shared/src/basic/hexdecoct.h
+++ b/src/libnm-systemd-shared/src/basic/hexdecoct.h
@@ -38,9 +38,13 @@ static inline ssize_t base64mem(const void *p, size_t l, char **ret) {
         return base64mem_full(p, l, SIZE_MAX, ret);
 }
 
-int base64_append(char **prefix, int plen,
-                  const void *p, size_t l,
-                  int margin, int width);
+ssize_t base64_append(
+                char **prefix,
+                size_t plen,
+                const void *p,
+                size_t l,
+                size_t margin,
+                size_t width);
 int unbase64mem_full(const char *p, size_t l, bool secure, void **mem, size_t *len);
 static inline int unbase64mem(const char *p, size_t l, void **mem, size_t *len) {
         return unbase64mem_full(p, l, false, mem, len);
diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.c b/src/libnm-systemd-shared/src/basic/hostname-util.c
index 1e94ce13..26e196f3 100644
--- a/src/libnm-systemd-shared/src/basic/hostname-util.c
+++ b/src/libnm-systemd-shared/src/basic/hostname-util.c
@@ -65,7 +65,7 @@ int gethostname_full(GetHostnameFlags flags, char **ret) {
         }
 
         if (FLAGS_SET(flags, GET_HOSTNAME_SHORT))
-                buf = strndup(s, strcspn(s, "."));
+                buf = strdupcspn(s, ".");
         else
                 buf = strdup(s);
         if (!buf)
@@ -197,6 +197,7 @@ bool is_localhost(const char *hostname) {
                 endswith_no_case(hostname, ".localhost.localdomain.");
 }
 
+#if 0 /* NM_IGNORED */
 int get_pretty_hostname(char **ret) {
         _cleanup_free_ char *n = NULL;
         int r;
@@ -213,3 +214,4 @@ int get_pretty_hostname(char **ret) {
         *ret = TAKE_PTR(n);
         return 0;
 }
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/hostname-util.h b/src/libnm-systemd-shared/src/basic/hostname-util.h
index a00b8523..bcac3d9f 100644
--- a/src/libnm-systemd-shared/src/basic/hostname-util.h
+++ b/src/libnm-systemd-shared/src/basic/hostname-util.h
@@ -60,4 +60,12 @@ static inline bool is_outbound_hostname(const char *hostname) {
         return STRCASE_IN_SET(hostname, "_outbound", "_outbound.");
 }
 
+static inline bool is_dns_stub_hostname(const char *hostname) {
+        return STRCASE_IN_SET(hostname, "_localdnsstub", "_localdnsstub.");
+}
+
+static inline bool is_dns_proxy_stub_hostname(const char *hostname) {
+        return STRCASE_IN_SET(hostname, "_localdnsproxy", "_localdnsproxy.");
+}
+
 int get_pretty_hostname(char **ret);
diff --git a/src/libnm-systemd-shared/src/basic/in-addr-util.c b/src/libnm-systemd-shared/src/basic/in-addr-util.c
index b25c803b..b863aec3 100644
--- a/src/libnm-systemd-shared/src/basic/in-addr-util.c
+++ b/src/libnm-systemd-shared/src/basic/in-addr-util.c
@@ -13,13 +13,13 @@
 #include "alloc-util.h"
 #include "errno-util.h"
 #include "in-addr-util.h"
+#include "logarithm.h"
 #include "macro.h"
 #include "parse-util.h"
 #include "random-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strxcpyx.h"
-#include "util.h"
 
 bool in4_addr_is_null(const struct in_addr *a) {
         assert(a);
@@ -907,14 +907,6 @@ int in_addr_prefix_from_string_auto_internal(
                         break;
                 case PREFIXLEN_REFUSE:
                         return -ENOANO; /* To distinguish this error from others. */
-                case PREFIXLEN_LEGACY:
-                        if (family == AF_INET) {
-                                r = in4_addr_default_prefixlen(&buffer.in, &k);
-                                if (r < 0)
-                                        return r;
-                        } else
-                                k = 0;
-                        break;
                 default:
                         assert_not_reached();
                 }
@@ -930,7 +922,7 @@ int in_addr_prefix_from_string_auto_internal(
 
 }
 
-static void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) {
+void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) {
         assert(a);
         assert(state);
 
@@ -938,7 +930,7 @@ static void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash
         siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state);
 }
 
-static int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) {
+int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) {
         int r;
 
         assert(x);
@@ -951,7 +943,18 @@ static int in_addr_data_compare_func(const struct in_addr_data *x, const struct
         return memcmp(&x->address, &y->address, FAMILY_ADDRESS_SIZE(x->family));
 }
 
-DEFINE_HASH_OPS(in_addr_data_hash_ops, struct in_addr_data, in_addr_data_hash_func, in_addr_data_compare_func);
+DEFINE_HASH_OPS(
+        in_addr_data_hash_ops,
+        struct in_addr_data,
+        in_addr_data_hash_func,
+        in_addr_data_compare_func);
+
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+        in_addr_data_hash_ops_free,
+        struct in_addr_data,
+        in_addr_data_hash_func,
+        in_addr_data_compare_func,
+        free);
 
 void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) {
         assert(addr);
@@ -967,7 +970,12 @@ int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b) {
         return memcmp(a, b, sizeof(*a));
 }
 
-DEFINE_HASH_OPS(in6_addr_hash_ops, struct in6_addr, in6_addr_hash_func, in6_addr_compare_func);
+DEFINE_HASH_OPS(
+        in6_addr_hash_ops,
+        struct in6_addr,
+        in6_addr_hash_func,
+        in6_addr_compare_func);
+
 DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
         in6_addr_hash_ops_free,
         struct in6_addr,
diff --git a/src/libnm-systemd-shared/src/basic/in-addr-util.h b/src/libnm-systemd-shared/src/basic/in-addr-util.h
index 19fa35f1..200b9eb6 100644
--- a/src/libnm-systemd-shared/src/basic/in-addr-util.h
+++ b/src/libnm-systemd-shared/src/basic/in-addr-util.h
@@ -8,7 +8,6 @@
 
 #include "hash-funcs.h"
 #include "macro.h"
-#include "util.h"
 
 union in_addr_union {
         struct in_addr in;
@@ -154,7 +153,6 @@ int in_addr_prefix_from_string(const char *p, int family, union in_addr_union *r
 typedef enum InAddrPrefixLenMode {
         PREFIXLEN_FULL,   /* Default to prefixlen of address size, 32 for IPv4 or 128 for IPv6, if not specified. */
         PREFIXLEN_REFUSE, /* Fail with -ENOANO if prefixlen is not specified. */
-        PREFIXLEN_LEGACY, /* Default to legacy default prefixlen calculation from address if not specified. */
 } InAddrPrefixLenMode;
 
 int in_addr_prefix_from_string_auto_internal(const char *p, InAddrPrefixLenMode mode, int *ret_family, union in_addr_union *ret_prefix, unsigned char *ret_prefixlen);
@@ -178,10 +176,13 @@ static inline size_t FAMILY_ADDRESS_SIZE(int family) {
  * See also oss-fuzz#11344. */
 #define IN_ADDR_NULL ((union in_addr_union) { .in6 = {} })
 
+void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state);
+int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y);
 void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state);
 int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b);
 
 extern const struct hash_ops in_addr_data_hash_ops;
+extern const struct hash_ops in_addr_data_hash_ops_free;
 extern const struct hash_ops in6_addr_hash_ops;
 extern const struct hash_ops in6_addr_hash_ops_free;
 
diff --git a/src/libnm-systemd-shared/src/basic/io-util.c b/src/libnm-systemd-shared/src/basic/io-util.c
index fc65a5e7..0c480091 100644
--- a/src/libnm-systemd-shared/src/basic/io-util.c
+++ b/src/libnm-systemd-shared/src/basic/io-util.c
@@ -167,6 +167,21 @@ int ppoll_usec(struct pollfd *fds, size_t nfds, usec_t timeout) {
 
         assert(fds || nfds == 0);
 
+        /* This is a wrapper around ppoll() that does primarily two things:
+         *
+         *  ✅ Takes a usec_t instead of a struct timespec
+         *
+         *  ✅ Guarantees that if an invalid fd is specified we return EBADF (i.e. converts POLLNVAL to
+         *     EBADF). This is done because EBADF is a programming error usually, and hence should bubble up
+         *     as error, and not be eaten up as non-error POLLNVAL event.
+         *
+         *  ⚠️ ⚠️ ⚠️ Note that this function does not add any special handling for EINTR. Don't forget
+         *  poll()/ppoll() will return with EINTR on any received signal always, there is no automatic
+         *  restarting via SA_RESTART available. Thus, typically you want to handle EINTR not as an error,
+         *  but just as reason to restart things, under the assumption you use a more appropriate mechanism
+         *  to handle signals, such as signalfd() or signal handlers. ⚠️ ⚠️ ⚠️
+         */
+
         if (nfds == 0)
                 return 0;
 
@@ -194,6 +209,9 @@ int fd_wait_for_event(int fd, int event, usec_t timeout) {
         };
         int r;
 
+        /* ⚠️ ⚠️ ⚠️ Keep in mind you almost certainly want to handle -EINTR gracefully in the caller, see
+         * ppoll_usec() above! ⚠️ ⚠️ ⚠️ */
+
         r = ppoll_usec(&pollfd, 1, timeout);
         if (r <= 0)
                 return r;
@@ -351,4 +369,14 @@ size_t iovw_size(struct iovec_wrapper *iovw) {
 
         return n;
 }
+
+void iovec_array_free(struct iovec *iov, size_t n) {
+        if (!iov)
+                return;
+
+        for (size_t i = 0; i < n; i++)
+                free(iov[i].iov_base);
+
+        free(iov);
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/io-util.h b/src/libnm-systemd-shared/src/basic/io-util.h
index 39728e06..3ad82679 100644
--- a/src/libnm-systemd-shared/src/basic/io-util.h
+++ b/src/libnm-systemd-shared/src/basic/io-util.h
@@ -74,10 +74,13 @@ static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) {
 
 }
 
-#define IOVEC_INIT(base, len) { .iov_base = (base), .iov_len = (len) }
-#define IOVEC_MAKE(base, len) (struct iovec) IOVEC_INIT(base, len)
-#define IOVEC_INIT_STRING(string) IOVEC_INIT((char*) string, strlen(string))
-#define IOVEC_MAKE_STRING(string) (struct iovec) IOVEC_INIT_STRING(string)
+#define IOVEC_NULL (struct iovec) {}
+#define IOVEC_MAKE(base, len) (struct iovec) { .iov_base = (base), .iov_len = (len) }
+#define IOVEC_MAKE_STRING(string)               \
+        ({                                      \
+                char *_s = (char*) (string);    \
+                IOVEC_MAKE(_s, strlen(_s));     \
+        })
 
 char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value);
 char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value);
@@ -91,8 +94,19 @@ struct iovec_wrapper *iovw_new(void);
 struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw);
 struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw);
 void iovw_free_contents(struct iovec_wrapper *iovw, bool free_vectors);
+
 int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len);
+static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) {
+        /* Move data into iovw or free on error */
+        int r = iovw_put(iovw, data, len);
+        if (r < 0)
+                free(data);
+        return r;
+}
+
 int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value);
 int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value);
 void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new);
 size_t iovw_size(struct iovec_wrapper *iovw);
+
+void iovec_array_free(struct iovec *iov, size_t n);
diff --git a/src/libnm-systemd-shared/src/basic/label.c b/src/libnm-systemd-shared/src/basic/label.c
new file mode 100644
index 00000000..a08a238f
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/label.c
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "nm-sd-adapt-shared.h"
+
+#include <errno.h>
+#include <stddef.h>
+
+#include "label.h"
+
+static const LabelOps *label_ops = NULL;
+
+int label_ops_set(const LabelOps *ops) {
+        if (label_ops)
+                return -EBUSY;
+
+        label_ops = ops;
+        return 0;
+}
+
+int label_ops_pre(int dir_fd, const char *path, mode_t mode) {
+        if (!label_ops || !label_ops->pre)
+                return 0;
+
+        return label_ops->pre(dir_fd, path, mode);
+}
+
+int label_ops_post(int dir_fd, const char *path) {
+        if (!label_ops || !label_ops->post)
+                return 0;
+
+        return label_ops->post(dir_fd, path);
+}
diff --git a/src/libnm-systemd-shared/src/basic/label.h b/src/libnm-systemd-shared/src/basic/label.h
new file mode 100644
index 00000000..9644e435
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/label.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <sys/types.h>
+
+typedef struct LabelOps {
+        int (*pre)(int dir_fd, const char *path, mode_t mode);
+        int (*post)(int dir_fd, const char *path);
+} LabelOps;
+
+int label_ops_set(const LabelOps *label_ops);
+
+int label_ops_pre(int dir_fd, const char *path, mode_t mode);
+int label_ops_post(int dir_fd, const char *path);
diff --git a/src/libnm-systemd-shared/src/basic/list.h b/src/libnm-systemd-shared/src/basic/list.h
index ca300396..e4e5dff3 100644
--- a/src/libnm-systemd-shared/src/basic/list.h
+++ b/src/libnm-systemd-shared/src/basic/list.h
@@ -1,8 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#include "macro.h"
-
 /* The head of the linked list. Use this in the structure that shall
  * contain the head of the linked list */
 #define LIST_HEAD(t,name)                                               \
@@ -28,26 +26,27 @@
 
 /* Prepend an item to the list */
 #define LIST_PREPEND(name,head,item)                                    \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_item = (item);     \
                 assert(_item);                                          \
                 if ((_item->name##_next = *_head))                      \
                         _item->name##_next->name##_prev = _item;        \
                 _item->name##_prev = NULL;                              \
                 *_head = _item;                                         \
-        } while (false)
+                _item;                                                  \
+        })
 
 /* Append an item to the list */
 #define LIST_APPEND(name,head,item)                                     \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_hhead = &(head), *_tail;             \
-                LIST_FIND_TAIL(name, *_hhead, _tail);                   \
+                _tail = LIST_FIND_TAIL(name, *_hhead);                  \
                 LIST_INSERT_AFTER(name, *_hhead, _tail, item);          \
-        } while (false)
+        })
 
 /* Remove an item from the list */
 #define LIST_REMOVE(name,head,item)                                     \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_item = (item);     \
                 assert(_item);                                          \
                 if (_item->name##_next)                                 \
@@ -59,37 +58,30 @@
                         *_head = _item->name##_next;                    \
                 }                                                       \
                 _item->name##_next = _item->name##_prev = NULL;         \
-        } while (false)
+                _item;                                                  \
+        })
 
 /* Find the head of the list */
-#define LIST_FIND_HEAD(name,item,head)                                  \
-        do {                                                            \
+#define LIST_FIND_HEAD(name,item)                                       \
+        ({                                                              \
                 typeof(*(item)) *_item = (item);                        \
-                if (!_item)                                             \
-                        (head) = NULL;                                  \
-                else {                                                  \
-                        while (_item->name##_prev)                      \
-                                _item = _item->name##_prev;             \
-                        (head) = _item;                                 \
-                }                                                       \
-        } while (false)
+                while (_item && _item->name##_prev)                     \
+                        _item = _item->name##_prev;                     \
+                _item;                                                  \
+        })
 
 /* Find the tail of the list */
-#define LIST_FIND_TAIL(name,item,tail)                                  \
-        do {                                                            \
+#define LIST_FIND_TAIL(name,item)                                       \
+        ({                                                              \
                 typeof(*(item)) *_item = (item);                        \
-                if (!_item)                                             \
-                        (tail) = NULL;                                  \
-                else {                                                  \
-                        while (_item->name##_next)                      \
-                                _item = _item->name##_next;             \
-                        (tail) = _item;                                 \
-                }                                                       \
-        } while (false)
+                while (_item && _item->name##_next)                     \
+                        _item = _item->name##_next;                     \
+                _item;                                                  \
+        })
 
 /* Insert an item after another one (a = where, b = what) */
 #define LIST_INSERT_AFTER(name,head,a,b)                                \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
                 assert(_b);                                             \
                 if (!_a) {                                              \
@@ -103,11 +95,12 @@
                         _b->name##_prev = _a;                           \
                         _a->name##_next = _b;                           \
                 }                                                       \
-        } while (false)
+                _b;                                                     \
+        })
 
 /* Insert an item before another one (a = where, b = what) */
 #define LIST_INSERT_BEFORE(name,head,a,b)                               \
-        do {                                                            \
+        ({                                                              \
                 typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \
                 assert(_b);                                             \
                 if (!_a) {                                              \
@@ -131,10 +124,14 @@
                         _b->name##_next = _a;                           \
                         _a->name##_prev = _b;                           \
                 }                                                       \
-        } while (false)
+                _b;                                                     \
+        })
 
-#define LIST_JUST_US(name,item)                                         \
-        (!(item)->name##_prev && !(item)->name##_next)
+#define LIST_JUST_US(name, item)                                        \
+        ({                                                              \
+                typeof(*(item)) *_item = (item);                        \
+                !(_item)->name##_prev && !(_item)->name##_next;         \
+        })
 
 /* The type of the iterator 'i' is automatically determined by the type of 'head', and declared in the
  * loop. Hence, do not declare the same variable in the outer scope. Sometimes, we set 'head' through
@@ -172,18 +169,19 @@
 
 /* Join two lists tail to head: a->b, c->d to a->b->c->d and de-initialise second list */
 #define LIST_JOIN(name,a,b)                                             \
-        do {                                                            \
+        ({                                                              \
                 assert(b);                                              \
                 if (!(a))                                               \
                         (a) = (b);                                      \
                 else {                                                  \
                         typeof(*(a)) *_head = (b), *_tail;              \
-                        LIST_FIND_TAIL(name, (a), _tail);               \
+                        _tail = LIST_FIND_TAIL(name, (a));              \
                         _tail->name##_next = _head;                     \
                         _head->name##_prev = _tail;                     \
                 }                                                       \
                 (b) = NULL;                                             \
-        } while (false)
+                a;                                                      \
+        })
 
 #define LIST_POP(name, a)                                               \
         ({                                                              \
@@ -193,3 +191,7 @@
                         LIST_REMOVE(name, *_a, _p);                     \
                 _p;                                                     \
         })
+
+/* Now include "macro.h", because we want our definition of assert() which the macros above use. We include
+ * it down here instead of up top, since macro.h pulls in log.h which in turn needs our own definitions. */
+#include "macro.h"
diff --git a/src/libnm-systemd-shared/src/basic/locale-util.c b/src/libnm-systemd-shared/src/basic/locale-util.c
index ae567345..2f3701eb 100644
--- a/src/libnm-systemd-shared/src/basic/locale-util.c
+++ b/src/libnm-systemd-shared/src/basic/locale-util.c
@@ -12,7 +12,7 @@
 #include <sys/mman.h>
 #include <sys/stat.h>
 
-#include "def.h"
+#include "constants.h"
 #include "dirent-util.h"
 #include "env-util.h"
 #include "fd-util.h"
@@ -98,7 +98,7 @@ static int add_locales_from_archive(Set *locales) {
         const struct locarhead *h;
         const struct namehashent *e;
         const void *p = MAP_FAILED;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         size_t sz = 0;
         struct stat st;
         int r;
@@ -290,8 +290,9 @@ void init_gettext(void) {
 #endif /* NM_IGNORED */
 
 bool is_locale_utf8(void) {
-        const char *set;
         static int cached_answer = -1;
+        const char *set;
+        int r;
 
         /* Note that we default to 'true' here, since today UTF8 is
          * pretty much supported everywhere. */
@@ -299,6 +300,13 @@ bool is_locale_utf8(void) {
         if (cached_answer >= 0)
                 goto out;
 
+        r = getenv_bool_secure("SYSTEMD_UTF8");
+        if (r >= 0) {
+                cached_answer = r;
+                goto out;
+        } else if (r != -ENXIO)
+                log_debug_errno(r, "Failed to parse $SYSTEMD_UTF8, ignoring: %m");
+
         if (!setlocale(LC_ALL, "")) {
                 cached_answer = true;
                 goto out;
diff --git a/src/libnm-systemd-shared/src/basic/lock-util.h b/src/libnm-systemd-shared/src/basic/lock-util.h
new file mode 100644
index 00000000..e7744476
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/lock-util.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <fcntl.h>
+
+typedef struct LockFile {
+        int dir_fd;
+        char *path;
+        int fd;
+        int operation;
+} LockFile;
+
+int make_lock_file_at(int dir_fd, const char *p, int operation, LockFile *ret);
+static inline int make_lock_file(const char *p, int operation, LockFile *ret) {
+        return make_lock_file_at(AT_FDCWD, p, operation, ret);
+}
+int make_lock_file_for(const char *p, int operation, LockFile *ret);
+void release_lock_file(LockFile *f);
+
+#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF }
+
+/* POSIX locks with the same interface as flock(). */
+int posix_lock(int fd, int operation);
+void posix_unlockpp(int **fd);
+
+#define CLEANUP_POSIX_UNLOCK(fd)                                   \
+        _cleanup_(posix_unlockpp) _unused_ int *CONCATENATE(_cleanup_posix_unlock_, UNIQ) = &(fd)
+
+/* Open File Description locks with the same interface as flock(). */
+int unposix_lock(int fd, int operation);
+void unposix_unlockpp(int **fd);
+
+#define CLEANUP_UNPOSIX_UNLOCK(fd)                                   \
+        _cleanup_(unposix_unlockpp) _unused_ int *CONCATENATE(_cleanup_unposix_unlock_, UNIQ) = &(fd)
+
+typedef enum LockType {
+        LOCK_BSD,
+        LOCK_POSIX,
+        LOCK_UNPOSIX,
+} LockType;
+
+int lock_generic(int fd, LockType type, int operation);
diff --git a/src/libnm-systemd-shared/src/basic/log.h b/src/libnm-systemd-shared/src/basic/log.h
index a6adacf6..eb7b51cb 100644
--- a/src/libnm-systemd-shared/src/basic/log.h
+++ b/src/libnm-systemd-shared/src/basic/log.h
@@ -7,8 +7,10 @@
 #include <string.h>
 #include <syslog.h>
 
+#include "list.h"
 #include "macro.h"
 #include "ratelimit.h"
+#include "stdio-util.h"
 
 /* Some structures we reference but don't want to pull in headers for */
 struct iovec;
@@ -49,8 +51,10 @@ static inline void clear_log_syntax_callback(dummy_t *dummy) {
 const char *log_target_to_string(LogTarget target) _const_;
 LogTarget log_target_from_string(const char *s) _pure_;
 void log_set_target(LogTarget target);
+void log_set_target_and_open(LogTarget target);
 int log_set_target_from_string(const char *e);
 LogTarget log_get_target(void) _pure_;
+void log_settle_target(void);
 
 void log_set_max_level(int level);
 int log_set_max_level_from_string(const char *e);
@@ -432,6 +436,7 @@ int log_emergency_level(void);
 
 #define log_oom() log_oom_internal(LOG_ERR, PROJECT_FILE, __LINE__, __func__)
 #define log_oom_debug() log_oom_internal(LOG_DEBUG, PROJECT_FILE, __LINE__, __func__)
+#define log_oom_warning() log_oom_internal(LOG_WARNING, PROJECT_FILE, __LINE__, __func__)
 
 bool log_on_console(void) _pure_;
 
@@ -516,15 +521,12 @@ typedef struct LogRateLimit {
         RateLimit ratelimit;
 } LogRateLimit;
 
-#define log_ratelimit_internal(_level, _error, _format, _file, _line, _func, ...)        \
+#define log_ratelimit_internal(_level, _error, _ratelimit, _format, _file, _line, _func, ...)        \
 ({                                                                              \
         int _log_ratelimit_error = (_error);                                    \
         int _log_ratelimit_level = (_level);                                    \
         static LogRateLimit _log_ratelimit = {                                  \
-                .ratelimit = {                                                  \
-                        .interval = 1 * USEC_PER_SEC,                           \
-                        .burst = 1,                                             \
-                },                                                              \
+                .ratelimit = (_ratelimit),                                      \
         };                                                                      \
         unsigned _num_dropped_errors = ratelimit_num_dropped(&_log_ratelimit.ratelimit); \
         if (_log_ratelimit_error != _log_ratelimit.error || _log_ratelimit_level != _log_ratelimit.level) { \
@@ -532,18 +534,145 @@ typedef struct LogRateLimit {
                 _log_ratelimit.error = _log_ratelimit_error;                    \
                 _log_ratelimit.level = _log_ratelimit_level;                    \
         }                                                                       \
-        if (ratelimit_below(&_log_ratelimit.ratelimit))                         \
+        if (log_get_max_level() == LOG_DEBUG || ratelimit_below(&_log_ratelimit.ratelimit)) \
                 _log_ratelimit_error = _num_dropped_errors > 0                  \
-                ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", __VA_ARGS__, _num_dropped_errors) \
-                : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, __VA_ARGS__); \
+                ? log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format " (Dropped %u similar message(s))", ##__VA_ARGS__, _num_dropped_errors) \
+                : log_internal(_log_ratelimit_level, _log_ratelimit_error, _file, _line, _func, _format, ##__VA_ARGS__); \
         _log_ratelimit_error;                                                   \
 })
 
-#define log_ratelimit_full_errno(level, error, format, ...)             \
+#define log_ratelimit_full_errno(level, error, _ratelimit, format, ...)             \
         ({                                                              \
                 int _level = (level), _e = (error);                     \
                 _e = (log_get_max_level() >= LOG_PRI(_level))           \
-                        ? log_ratelimit_internal(_level, _e, format, PROJECT_FILE, __LINE__, __func__, __VA_ARGS__) \
+                        ? log_ratelimit_internal(_level, _e, _ratelimit, format, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__) \
                         : -ERRNO_VALUE(_e);                             \
                 _e < 0 ? _e : -ESTRPIPE;                                \
         })
+
+#define log_ratelimit_full(level, _ratelimit, format, ...)                          \
+        log_ratelimit_full_errno(level, 0, _ratelimit, format, ##__VA_ARGS__)
+
+/* Normal logging */
+#define log_ratelimit_info(...)      log_ratelimit_full(LOG_INFO,    __VA_ARGS__)
+#define log_ratelimit_notice(...)    log_ratelimit_full(LOG_NOTICE,  __VA_ARGS__)
+#define log_ratelimit_warning(...)   log_ratelimit_full(LOG_WARNING, __VA_ARGS__)
+#define log_ratelimit_error(...)     log_ratelimit_full(LOG_ERR,     __VA_ARGS__)
+#define log_ratelimit_emergency(...) log_ratelimit_full(log_emergency_level(), __VA_ARGS__)
+
+/* Logging triggered by an errno-like error */
+#define log_ratelimit_info_errno(error, ...)      log_ratelimit_full_errno(LOG_INFO,    error, __VA_ARGS__)
+#define log_ratelimit_notice_errno(error, ...)    log_ratelimit_full_errno(LOG_NOTICE,  error, __VA_ARGS__)
+#define log_ratelimit_warning_errno(error, ...)   log_ratelimit_full_errno(LOG_WARNING, error, __VA_ARGS__)
+#define log_ratelimit_error_errno(error, ...)     log_ratelimit_full_errno(LOG_ERR,     error, __VA_ARGS__)
+#define log_ratelimit_emergency_errno(error, ...) log_ratelimit_full_errno(log_emergency_level(), error, __VA_ARGS__)
+
+const char *_log_set_prefix(const char *prefix, bool force);
+static inline const char *_log_unset_prefixp(const char **p) {
+        assert(p);
+        _log_set_prefix(*p, true);
+        return NULL;
+}
+
+#define LOG_SET_PREFIX(prefix) \
+        _cleanup_(_log_unset_prefixp) _unused_ const char *CONCATENATE(_cleanup_log_unset_prefix_, UNIQ) = _log_set_prefix(prefix, false);
+
+/*
+ * The log context allows attaching extra metadata to log messages written to the journal via log.h. We keep
+ * track of a thread local log context onto which we can push extra metadata fields that should be logged.
+ *
+ * LOG_CONTEXT_PUSH() will add the provided field to the log context and will remove it again when the
+ * current block ends. LOG_CONTEXT_PUSH_STRV() will do the same but for all fields in the given strv.
+ * LOG_CONTEXT_PUSHF() is like LOG_CONTEXT_PUSH() but takes a format string and arguments.
+ *
+ * Using the macros is as simple as putting them anywhere inside a block to add a field to all following log
+ * messages logged from inside that block.
+ *
+ * void myfunction(...) {
+ *         ...
+ *
+ *         LOG_CONTEXT_PUSHF("MYMETADATA=%s", "abc");
+ *
+ *         // Every journal message logged will now have the MYMETADATA=abc
+ *         // field included.
+ * }
+ *
+ * One special case to note is async code, where we use callbacks that are invoked to continue processing
+ * when some event occurs. For async code, there's usually an associated "userdata" struct containing all the
+ * information associated with the async operation. In this "userdata" struct, we can store a log context
+ * allocated with log_context_new() and freed with log_context_free(). We can then add and remove fields to
+ * the `fields` member of the log context object and all those fields will be logged along with each log
+ * message.
+ */
+
+typedef struct LogContext LogContext;
+
+bool log_context_enabled(void);
+
+LogContext* log_context_new(const char *key, const char *value);
+LogContext* log_context_new_strv(char **fields, bool owned);
+LogContext* log_context_new_iov(struct iovec *input_iovec, size_t n_input_iovec, bool owned);
+
+/* Same as log_context_new(), but frees the given fields strv/iovec on failure. */
+LogContext* log_context_new_strv_consume(char **fields);
+LogContext* log_context_new_iov_consume(struct iovec *input_iovec, size_t n_input_iovec);
+
+LogContext *log_context_ref(LogContext *c);
+LogContext *log_context_unref(LogContext *c);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(LogContext*, log_context_unref);
+
+/* Returns the number of attached log context objects. */
+size_t log_context_num_contexts(void);
+/* Returns the number of fields in all attached log contexts. */
+size_t log_context_num_fields(void);
+
+#define LOG_CONTEXT_PUSH(...) \
+        LOG_CONTEXT_PUSH_STRV(STRV_MAKE(__VA_ARGS__))
+
+#define LOG_CONTEXT_PUSHF(...) \
+        LOG_CONTEXT_PUSH(snprintf_ok((char[LINE_MAX]) {}, LINE_MAX, __VA_ARGS__))
+
+#define _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new(key, value);
+
+#define LOG_CONTEXT_PUSH_KEY_VALUE(key, value) \
+        _LOG_CONTEXT_PUSH_KEY_VALUE(key, value, UNIQ_T(c, UNIQ))
+
+#define _LOG_CONTEXT_PUSH_STRV(strv, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv(strv, /*owned=*/ false);
+
+#define LOG_CONTEXT_PUSH_STRV(strv) \
+        _LOG_CONTEXT_PUSH_STRV(strv, UNIQ_T(c, UNIQ))
+
+#define _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov(input_iovec, n_input_iovec, /*owned=*/ false);
+
+#define LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec) \
+        _LOG_CONTEXT_PUSH_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
+
+/* LOG_CONTEXT_CONSUME_STR()/LOG_CONTEXT_CONSUME_STRV()/LOG_CONTEXT_CONSUME_IOV() are identical to
+ * LOG_CONTEXT_PUSH_STR()/LOG_CONTEXT_PUSH_STRV()/LOG_CONTEXT_PUSH_IOV() except they take ownership of the
+ * given str/strv argument.
+ */
+
+#define _LOG_CONTEXT_CONSUME_STR(s, c, strv) \
+        _unused_ _cleanup_strv_free_ strv = strv_new(s);                                                \
+        if (!strv)                                                                                      \
+                free(s);                                                                                \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(TAKE_PTR(strv))
+
+#define LOG_CONTEXT_CONSUME_STR(s) \
+        _LOG_CONTEXT_CONSUME_STR(s, UNIQ_T(c, UNIQ), UNIQ_T(sv, UNIQ))
+
+#define _LOG_CONTEXT_CONSUME_STRV(strv, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_strv_consume(strv);
+
+#define LOG_CONTEXT_CONSUME_STRV(strv) \
+        _LOG_CONTEXT_CONSUME_STRV(strv, UNIQ_T(c, UNIQ))
+
+#define _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, c) \
+        _unused_ _cleanup_(log_context_unrefp) LogContext *c = log_context_new_iov_consume(input_iovec, n_input_iovec);
+
+#define LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec) \
+        _LOG_CONTEXT_CONSUME_IOV(input_iovec, n_input_iovec, UNIQ_T(c, UNIQ))
diff --git a/src/libnm-systemd-shared/src/basic/macro.h b/src/libnm-systemd-shared/src/basic/macro.h
index 7380da10..ce7350cb 100644
--- a/src/libnm-systemd-shared/src/basic/macro.h
+++ b/src/libnm-systemd-shared/src/basic/macro.h
@@ -9,32 +9,9 @@
 #include <sys/sysmacros.h>
 #include <sys/types.h>
 
+#include "constants.h"
 #include "macro-fundamental.h"
 
-#if !defined(HAS_FEATURE_MEMORY_SANITIZER)
-#  if defined(__has_feature)
-#    if __has_feature(memory_sanitizer)
-#      define HAS_FEATURE_MEMORY_SANITIZER 1
-#    endif
-#  endif
-#  if !defined(HAS_FEATURE_MEMORY_SANITIZER)
-#    define HAS_FEATURE_MEMORY_SANITIZER 0
-#  endif
-#endif
-
-#if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
-#  ifdef __SANITIZE_ADDRESS__
-#      define HAS_FEATURE_ADDRESS_SANITIZER 1
-#  elif defined(__has_feature)
-#    if __has_feature(address_sanitizer)
-#      define HAS_FEATURE_ADDRESS_SANITIZER 1
-#    endif
-#  endif
-#  if !defined(HAS_FEATURE_ADDRESS_SANITIZER)
-#    define HAS_FEATURE_ADDRESS_SANITIZER 0
-#  endif
-#endif
-
 /* Note: on GCC "no_sanitize_address" is a function attribute only, on llvm it may also be applied to global
  * variables. We define a specific macro which knows this. Note that on GCC we don't need this decorator so much, since
  * our primary usecase for this attribute is registration structures placed in named ELF sections which shall not be
@@ -88,10 +65,14 @@
         _Pragma("GCC diagnostic push")
 #endif
 
-#define DISABLE_WARNING_TYPE_LIMITS \
+#define DISABLE_WARNING_TYPE_LIMITS                                     \
         _Pragma("GCC diagnostic push");                                 \
         _Pragma("GCC diagnostic ignored \"-Wtype-limits\"")
 
+#define DISABLE_WARNING_ADDRESS                                         \
+        _Pragma("GCC diagnostic push");                                 \
+        _Pragma("GCC diagnostic ignored \"-Waddress\"")
+
 #define REENABLE_WARNING                                                \
         _Pragma("GCC diagnostic pop")
 #else
@@ -203,12 +184,12 @@ static inline int __coverity_check_and_return__(int condition) {
 #define assert_message_se(expr, message)                                \
         do {                                                            \
                 if (_unlikely_(!(expr)))                                \
-                        log_assert_failed(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__); \
+                        log_assert_failed(message, PROJECT_FILE, __LINE__, __func__); \
         } while (false)
 
 #define assert_log(expr, message) ((_likely_(expr))                     \
         ? (true)                                                        \
-        : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__), false))
+        : (log_assert_failed_return(message, PROJECT_FILE, __LINE__, __func__), false))
 
 #endif  /* __COVERITY__ */
 
@@ -223,7 +204,7 @@ static inline int __coverity_check_and_return__(int condition) {
 #endif
 
 #define assert_not_reached()                                            \
-        log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __PRETTY_FUNCTION__)
+        log_assert_failed_unreachable(PROJECT_FILE, __LINE__, __func__)
 
 #define assert_return(expr, r)                                          \
         do {                                                            \
@@ -276,6 +257,11 @@ static inline int __coverity_check_and_return__(int condition) {
 #define char_array_0(x) x[sizeof(x)-1] = 0;
 
 #define sizeof_field(struct_type, member) sizeof(((struct_type *) 0)->member)
+#define endoffsetof_field(struct_type, member) (offsetof(struct_type, member) + sizeof_field(struct_type, member))
+
+/* Maximum buffer size needed for formatting an unsigned integer type as hex, including space for '0x'
+ * prefix and trailing NUL suffix. */
+#define HEXADECIMAL_STR_MAX(type) (2 + sizeof(type) * 2 + 1)
 
 /* Returns the number of chars needed to format variables of the specified type as a decimal string. Adds in
  * extra space for a negative '-' prefix for signed types. Includes space for the trailing NUL. */
@@ -324,19 +310,14 @@ static inline int __coverity_check_and_return__(int condition) {
              p != (typeof(p)) POINTER_MAX;                                               \
              p = *(++_l))
 
-/* Define C11 thread_local attribute even on older gcc compiler
- * version */
-#ifndef thread_local
-/*
- * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__
- * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769
- */
-#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
-#define thread_local _Thread_local
-#else
-#define thread_local __thread
-#endif
-#endif
+#define _FOREACH_ARRAY(i, array, num, m, end)                           \
+        for (typeof(array[0]) *i = (array), *end = ({                   \
+                                typeof(num) m = (num);                  \
+                                (i && m > 0) ? i + m : NULL;            \
+                        }); end && i < end; i++)
+
+#define FOREACH_ARRAY(i, array, num)                                    \
+        _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ))
 
 #define DEFINE_TRIVIAL_DESTRUCTOR(name, type, func)             \
         static inline void name(type *p) {                      \
@@ -350,10 +331,14 @@ static inline int __coverity_check_and_return__(int condition) {
                         *p = func(*p);                          \
         }
 
-/* When func() doesn't return the appropriate type, set variable to empty afterwards */
+/* When func() doesn't return the appropriate type, set variable to empty afterwards.
+ * The func() may be provided by a dynamically loaded shared library, hence add an assertion. */
 #define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(type, func, empty)     \
         static inline void func##p(type *p) {                   \
                 if (*p != (empty)) {                            \
+                        DISABLE_WARNING_ADDRESS;                \
+                        assert(func);                           \
+                        REENABLE_WARNING;                       \
                         func(*p);                               \
                         *p = (empty);                           \
                 }                                               \
@@ -442,7 +427,7 @@ typedef struct {
 
 assert_cc(sizeof(dummy_t) == 0);
 
-/* A little helper for subtracting 1 off a pointer in a safe UB-free way. This is intended to be used for for
+/* A little helper for subtracting 1 off a pointer in a safe UB-free way. This is intended to be used for
  * loops that count down from a high pointer until some base. A naive loop would implement this like this:
  *
  * for (p = end-1; p >= base; p--) …
@@ -458,4 +443,13 @@ assert_cc(sizeof(dummy_t) == 0);
                 _q && _q > (base) ? &_q[-1] : NULL;      \
         })
 
+/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly
+ * convertible. The iteration variable 'entry' must already be defined. */
+#define VA_ARGS_FOREACH(entry, ...)                                     \
+        _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__)
+#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...)         \
+        for (typeof(entry) _entries_[] = { __VA_ARGS__ }, *_current_ = _entries_; \
+             ((long)(_current_ - _entries_) < (long)ELEMENTSOF(_entries_)) && ({ entry = *_current_; true; }); \
+             _current_++)
+
 #include "log.h"
diff --git a/src/libnm-systemd-shared/src/basic/memory-util.c b/src/libnm-systemd-shared/src/basic/memory-util.c
index 2d41a26a..c1e0a742 100644
--- a/src/libnm-systemd-shared/src/basic/memory-util.c
+++ b/src/libnm-systemd-shared/src/basic/memory-util.c
@@ -5,6 +5,7 @@
 #include <unistd.h>
 
 #include "memory-util.h"
+#include "missing_threads.h"
 
 size_t page_size(void) {
         static thread_local size_t pgsz = 0;
@@ -40,21 +41,3 @@ bool memeqbyte(uint8_t byte, const void *data, size_t length) {
         /* Now we know first 16 bytes match, memcmp() with self.  */
         return memcmp(data, p + 16, length) == 0;
 }
-
-#if !HAVE_EXPLICIT_BZERO
-/*
- * The pointer to memset() is volatile so that compiler must de-reference the pointer and can't assume that
- * it points to any function in particular (such as memset(), which it then might further "optimize"). This
- * approach is inspired by openssl's crypto/mem_clr.c.
- */
-typedef void *(*memset_t)(void *,int,size_t);
-
-static volatile memset_t memset_func = memset;
-
-void* explicit_bzero_safe(void *p, size_t l) {
-        if (l > 0)
-                memset_func(p, '\0', l);
-
-        return p;
-}
-#endif
diff --git a/src/libnm-systemd-shared/src/basic/memory-util.h b/src/libnm-systemd-shared/src/basic/memory-util.h
index 6e3280b9..d26a0918 100644
--- a/src/libnm-systemd-shared/src/basic/memory-util.h
+++ b/src/libnm-systemd-shared/src/basic/memory-util.h
@@ -9,6 +9,7 @@
 
 #include "alloc-util.h"
 #include "macro.h"
+#include "memory-util-fundamental.h"
 
 size_t page_size(void) _pure_;
 #define PAGE_ALIGN(l) ALIGN_TO((l), page_size())
@@ -91,17 +92,6 @@ static inline void *mempmem_safe(const void *haystack, size_t haystacklen, const
         return (uint8_t*) p + needlelen;
 }
 
-#if HAVE_EXPLICIT_BZERO
-static inline void* explicit_bzero_safe(void *p, size_t l) {
-        if (l > 0)
-                explicit_bzero(p, l);
-
-        return p;
-}
-#else
-void *explicit_bzero_safe(void *p, size_t l);
-#endif
-
 static inline void* erase_and_free(void *p) {
         size_t l;
 
@@ -121,3 +111,37 @@ static inline void erase_and_freep(void *p) {
 static inline void erase_char(char *p) {
         explicit_bzero_safe(p, sizeof(char));
 }
+
+/* An automatic _cleanup_-like logic for destroy arrays (i.e. pointers + size) when leaving scope */
+typedef struct ArrayCleanup {
+        void **parray;
+        size_t *pn;
+        free_array_func_t pfunc;
+} ArrayCleanup;
+
+static inline void array_cleanup(const ArrayCleanup *c) {
+        assert(c);
+
+        assert(!c->parray == !c->pn);
+
+        if (!c->parray)
+                return;
+
+        if (*c->parray) {
+                assert(c->pfunc);
+                c->pfunc(*c->parray, *c->pn);
+                *c->parray = NULL;
+        }
+
+        *c->pn = 0;
+}
+
+#define CLEANUP_ARRAY(array, n, func)                                   \
+        _cleanup_(array_cleanup) _unused_ const ArrayCleanup CONCATENATE(_cleanup_array_, UNIQ) = { \
+                .parray = (void**) &(array),                            \
+                .pn = &(n),                                             \
+                .pfunc = (free_array_func_t) ({                         \
+                                void (*_f)(typeof(array[0]) *a, size_t b) = func; \
+                                _f;                                     \
+                        }),                                             \
+        }
diff --git a/src/libnm-systemd-shared/src/basic/mempool.c b/src/libnm-systemd-shared/src/basic/mempool.c
index 53a719e0..e467d706 100644
--- a/src/libnm-systemd-shared/src/basic/mempool.c
+++ b/src/libnm-systemd-shared/src/basic/mempool.c
@@ -5,6 +5,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+#include "format-util.h"
 #include "macro.h"
 #include "memory-util.h"
 #include "mempool.h"
@@ -15,21 +16,26 @@ struct pool {
         size_t n_used;
 };
 
+static void* pool_ptr(struct pool *p) {
+        return ((uint8_t*) ASSERT_PTR(p)) + ALIGN(sizeof(struct pool));
+}
+
 void* mempool_alloc_tile(struct mempool *mp) {
         size_t i;
 
         /* When a tile is released we add it to the list and simply
          * place the next pointer at its offset 0. */
 
+        assert(mp);
         assert(mp->tile_size >= sizeof(void*));
         assert(mp->at_least > 0);
 
         if (mp->freelist) {
-                void *r;
+                void *t;
 
-                r = mp->freelist;
-                mp->freelist = * (void**) mp->freelist;
-                return r;
+                t = mp->freelist;
+                mp->freelist = *(void**) mp->freelist;
+                return t;
         }
 
         if (_unlikely_(!mp->first_pool) ||
@@ -55,7 +61,7 @@ void* mempool_alloc_tile(struct mempool *mp) {
 
         i = mp->first_pool->n_used++;
 
-        return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size;
+        return (uint8_t*) pool_ptr(mp->first_pool) + i*mp->tile_size;
 }
 
 void* mempool_alloc0_tile(struct mempool *mp) {
@@ -67,19 +73,105 @@ void* mempool_alloc0_tile(struct mempool *mp) {
         return p;
 }
 
-void mempool_free_tile(struct mempool *mp, void *p) {
-        * (void**) p = mp->freelist;
+void* mempool_free_tile(struct mempool *mp, void *p) {
+        assert(mp);
+
+        if (!p)
+                return NULL;
+
+        *(void**) p = mp->freelist;
         mp->freelist = p;
+
+        return NULL;
+}
+
+static bool pool_contains(struct mempool *mp, struct pool *p, void *ptr) {
+        size_t off;
+        void *a;
+
+        assert(mp);
+        assert(p);
+
+        if (!ptr)
+                return false;
+
+        a = pool_ptr(p);
+        if ((uint8_t*) ptr < (uint8_t*) a)
+                return false;
+
+        off = (uint8_t*) ptr - (uint8_t*) a;
+        if (off >= mp->tile_size * p->n_tiles)
+                return false;
+
+        assert(off % mp->tile_size == 0);
+        return true;
+}
+
+static bool pool_is_unused(struct mempool *mp, struct pool *p) {
+        assert(mp);
+        assert(p);
+
+        if (p->n_used == 0)
+                return true;
+
+        /* Check if all tiles in this specific pool are in the freelist. */
+        size_t n = 0;
+        void *i = mp->freelist;
+        while (i) {
+                if (pool_contains(mp, p, i))
+                        n++;
+
+                i = *(void**) i;
+        }
+
+        assert(n <= p->n_used);
+
+        return n == p->n_used;
+}
+
+static void pool_unlink(struct mempool *mp, struct pool *p) {
+        size_t m = 0;
+
+        assert(mp);
+        assert(p);
+
+        if (p->n_used == 0)
+                return;
+
+        void **i = &mp->freelist;
+        while (*i) {
+                void *d = *i;
+
+                if (pool_contains(mp, p, d)) {
+                        *i = *(void**) d;
+                        m++;
+
+                        if (m == p->n_used)
+                                break;
+                } else
+                        i = (void**) d;
+        }
 }
 
-#if VALGRIND
-void mempool_drop(struct mempool *mp) {
-        struct pool *p = mp->first_pool;
-        while (p) {
-                struct pool *n;
-                n = p->next;
-                free(p);
-                p = n;
+void mempool_trim(struct mempool *mp) {
+        size_t trimmed = 0, left = 0;
+
+        assert(mp);
+
+        struct pool **p = &mp->first_pool;
+        while (*p) {
+                struct pool *d = *p;
+
+                if (pool_is_unused(mp, d)) {
+                        trimmed += d->n_tiles * mp->tile_size;
+                        pool_unlink(mp, d);
+                        *p = d->next;
+                        free(d);
+                } else {
+                        left += d->n_tiles * mp->tile_size;
+                        p = &d->next;
+                }
         }
+
+        log_debug("Trimmed %s from memory pool %p. (%s left)", FORMAT_BYTES(trimmed), mp, FORMAT_BYTES(left));
 }
-#endif
diff --git a/src/libnm-systemd-shared/src/basic/mempool.h b/src/libnm-systemd-shared/src/basic/mempool.h
index 539ccbdf..ba588af4 100644
--- a/src/libnm-systemd-shared/src/basic/mempool.h
+++ b/src/libnm-systemd-shared/src/basic/mempool.h
@@ -10,12 +10,12 @@ struct mempool {
         struct pool *first_pool;
         void *freelist;
         size_t tile_size;
-        unsigned at_least;
+        size_t at_least;
 };
 
 void* mempool_alloc_tile(struct mempool *mp);
 void* mempool_alloc0_tile(struct mempool *mp);
-void mempool_free_tile(struct mempool *mp, void *p);
+void* mempool_free_tile(struct mempool *mp, void *p);
 
 #define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \
 static struct mempool pool_name = { \
@@ -25,6 +25,4 @@ static struct mempool pool_name = { \
 
 __attribute__((weak)) bool mempool_enabled(void);
 
-#if VALGRIND
-void mempool_drop(struct mempool *mp);
-#endif
+void mempool_trim(struct mempool *mp);
diff --git a/src/libnm-systemd-shared/src/basic/missing_fcntl.h b/src/libnm-systemd-shared/src/basic/missing_fcntl.h
index 00937d2a..24b2dc31 100644
--- a/src/libnm-systemd-shared/src/basic/missing_fcntl.h
+++ b/src/libnm-systemd-shared/src/basic/missing_fcntl.h
@@ -25,6 +25,14 @@
 #define F_SEAL_WRITE    0x0008  /* prevent writes */
 #endif
 
+#ifndef F_SEAL_FUTURE_WRITE
+#define F_SEAL_FUTURE_WRITE 0x0010 /* prevent future writes while mapped */
+#endif
+
+#ifndef F_SEAL_EXEC
+#define F_SEAL_EXEC     0x0020  /* prevent chmod modifying exec bits */
+#endif
+
 #ifndef F_OFD_GETLK
 #define F_OFD_GETLK     36
 #define F_OFD_SETLK     37
@@ -58,3 +66,12 @@
 #ifndef O_TMPFILE
 #define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
 #endif
+
+/* So O_LARGEFILE is generally implied by glibc, and defined to zero hence, because we only build in LFS
+ * mode. However, when invoking fcntl(F_GETFL) the flag is ORed into the result anyway — glibc does not mask
+ * it away. Which sucks. Let's define the actual value here, so that we can mask it ourselves. */
+#if O_LARGEFILE != 0
+#define RAW_O_LARGEFILE O_LARGEFILE
+#else
+#define RAW_O_LARGEFILE 0100000
+#endif
diff --git a/src/libnm-systemd-shared/src/basic/missing_syscall.h b/src/libnm-systemd-shared/src/basic/missing_syscall.h
index 107f4b01..610a7cef 100644
--- a/src/libnm-systemd-shared/src/basic/missing_syscall.h
+++ b/src/libnm-systemd-shared/src/basic/missing_syscall.h
@@ -370,6 +370,20 @@ static inline int missing_rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *info)
 /* ======================================================================= */
 
 #if 0 /* NM_IGNORED */
+#if !HAVE_RT_TGSIGQUEUEINFO
+static inline int missing_rt_tgsigqueueinfo(pid_t tgid, pid_t tid, int sig, siginfo_t *info) {
+#  if defined __NR_rt_tgsigqueueinfo && __NR_rt_tgsigqueueinfo >= 0
+        return syscall(__NR_rt_tgsigqueueinfo, tgid, tid, sig, info);
+#  else
+#    error "__NR_rt_tgsigqueueinfo not defined"
+#  endif
+}
+
+#  define rt_tgsigqueueinfo missing_rt_tgsigqueueinfo
+#endif
+
+/* ======================================================================= */
+
 #if !HAVE_EXECVEAT
 static inline int missing_execveat(int dirfd, const char *pathname,
                                    char *const argv[], char *const envp[],
@@ -419,44 +433,6 @@ static inline int missing_close_range(int first_fd, int end_fd, unsigned flags)
 
 /* ======================================================================= */
 
-#if !HAVE_EPOLL_PWAIT2
-
-/* Defined to be equivalent to the kernel's _NSIG_WORDS, i.e. the size of the array of longs that is
- * encapsulated by sigset_t. */
-#define KERNEL_NSIG_WORDS (64 / (sizeof(long) * 8))
-#define KERNEL_NSIG_BYTES (KERNEL_NSIG_WORDS * sizeof(long))
-
-struct epoll_event;
-
-static inline int missing_epoll_pwait2(
-                int fd,
-                struct epoll_event *events,
-                int maxevents,
-                const struct timespec *timeout,
-                const sigset_t *sigset) {
-
-#  if defined(__NR_epoll_pwait2) && HAVE_LINUX_TIME_TYPES_H
-        if (timeout) {
-                /* Convert from userspace timespec to kernel timespec */
-                struct __kernel_timespec ts = {
-                        .tv_sec = timeout->tv_sec,
-                        .tv_nsec = timeout->tv_nsec,
-                };
-
-                return syscall(__NR_epoll_pwait2, fd, events, maxevents, &ts, sigset, sigset ? KERNEL_NSIG_BYTES : 0);
-        } else
-                return syscall(__NR_epoll_pwait2, fd, events, maxevents, NULL, sigset, sigset ? KERNEL_NSIG_BYTES : 0);
-#  else
-        errno = ENOSYS;
-        return -1;
-#  endif
-}
-
-#  define epoll_pwait2 missing_epoll_pwait2
-#endif
-
-/* ======================================================================= */
-
 #if !HAVE_MOUNT_SETATTR
 
 #if !HAVE_STRUCT_MOUNT_ATTR
@@ -600,6 +576,78 @@ static inline int missing_move_mount(
 
 /* ======================================================================= */
 
+#if !HAVE_FSOPEN
+
+#ifndef FSOPEN_CLOEXEC
+#define FSOPEN_CLOEXEC 0x00000001
+#endif
+
+static inline int missing_fsopen(const char *fsname, unsigned flags) {
+#  if defined __NR_fsopen && __NR_fsopen >= 0
+        return syscall(__NR_fsopen, fsname, flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define fsopen missing_fsopen
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_FSCONFIG
+
+#ifndef FSCONFIG_SET_FLAG
+#define FSCONFIG_SET_FLAG 0 /* Set parameter, supplying no value */
+#endif
+
+#ifndef FSCONFIG_SET_STRING
+#define FSCONFIG_SET_STRING 1 /* Set parameter, supplying a string value */
+#endif
+
+#ifndef FSCONFIG_SET_FD
+#define FSCONFIG_SET_FD 5 /* Set parameter, supplying an object by fd */
+#endif
+
+#ifndef FSCONFIG_CMD_CREATE
+#define FSCONFIG_CMD_CREATE 6 /* Invoke superblock creation */
+#endif
+
+static inline int missing_fsconfig(int fd, unsigned cmd, const char *key, const void *value, int aux) {
+#  if defined __NR_fsconfig && __NR_fsconfig >= 0
+        return syscall(__NR_fsconfig, fd, cmd, key, value, aux);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define fsconfig missing_fsconfig
+#endif
+
+/* ======================================================================= */
+
+#if !HAVE_FSMOUNT
+
+#ifndef FSMOUNT_CLOEXEC
+#define FSMOUNT_CLOEXEC 0x00000001
+#endif
+
+static inline int missing_fsmount(int fd, unsigned flags, unsigned ms_flags) {
+#  if defined __NR_fsmount && __NR_fsmount >= 0
+        return syscall(__NR_fsmount, fd, flags, ms_flags);
+#  else
+        errno = ENOSYS;
+        return -1;
+#  endif
+}
+
+#  define fsmount missing_fsmount
+#endif
+
+/* ======================================================================= */
+
 #if !HAVE_GETDENTS64
 
 static inline ssize_t missing_getdents64(int fd, void *buffer, size_t length) {
diff --git a/src/libnm-systemd-shared/src/basic/missing_threads.h b/src/libnm-systemd-shared/src/basic/missing_threads.h
new file mode 100644
index 00000000..fb3b7224
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/missing_threads.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+/* If threads.h doesn't exist, then define our own thread_local to match C11's thread_local. */
+#if HAVE_THREADS_H
+#  include <threads.h>
+#elif !(defined(thread_local))
+/* Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__
+ * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 */
+#  if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16))
+#    define thread_local _Thread_local
+#  else
+#    define thread_local __thread
+#  endif
+#endif
diff --git a/src/libnm-systemd-shared/src/basic/origin-id.h b/src/libnm-systemd-shared/src/basic/origin-id.h
new file mode 100644
index 00000000..c55b0a36
--- /dev/null
+++ b/src/libnm-systemd-shared/src/basic/origin-id.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <pthread.h>
+
+#include "random-util.h"
+
+/* This pattern needs to be repeated exactly in multiple modules, so macro it.
+ * To ensure an object is not passed into a different module (e.g.: when two shared objects statically
+ * linked to libsystemd get loaded in the same process, and the object created by one is passed to the
+ * other, see https://github.com/systemd/systemd/issues/27216), create a random static global random
+ * (mixed with PID, so that we can also check for reuse after fork) that is stored in the object and
+ * checked by public API on use. */
+#define _DEFINE_ORIGIN_ID_HELPERS(type, name, scope)                  \
+static uint64_t origin_id;                                            \
+                                                                      \
+static void origin_id_initialize(void) {                              \
+        origin_id = random_u64();                                     \
+}                                                                     \
+                                                                      \
+static uint64_t origin_id_query(void) {                               \
+        static pthread_once_t once = PTHREAD_ONCE_INIT;               \
+        assert_se(pthread_once(&once, origin_id_initialize) == 0);    \
+        return origin_id ^ getpid_cached();                           \
+}                                                                     \
+                                                                      \
+scope bool name##_origin_changed(type *p) {                           \
+        assert(p);                                                    \
+        return p->origin_id != origin_id_query();                     \
+}
+
+#define DEFINE_ORIGIN_ID_HELPERS(type, name)                          \
+        _DEFINE_ORIGIN_ID_HELPERS(type, name,);
+
+#define DEFINE_PRIVATE_ORIGIN_ID_HELPERS(type, name)                  \
+        _DEFINE_ORIGIN_ID_HELPERS(type, name, static);
diff --git a/src/libnm-systemd-shared/src/basic/parse-util.c b/src/libnm-systemd-shared/src/basic/parse-util.c
index 968f01c6..2b22039c 100644
--- a/src/libnm-systemd-shared/src/basic/parse-util.c
+++ b/src/libnm-systemd-shared/src/basic/parse-util.c
@@ -53,7 +53,6 @@ int parse_pid(const char *s, pid_t* ret_pid) {
         int r;
 
         assert(s);
-        assert(ret_pid);
 
         r = safe_atolu(s, &ul);
         if (r < 0)
@@ -67,7 +66,8 @@ int parse_pid(const char *s, pid_t* ret_pid) {
         if (!pid_is_valid(pid))
                 return -ERANGE;
 
-        *ret_pid = pid;
+        if (ret_pid)
+                *ret_pid = pid;
         return 0;
 }
 
@@ -261,6 +261,26 @@ int parse_size(const char *t, uint64_t base, uint64_t *size) {
         return 0;
 }
 
+int parse_sector_size(const char *t, uint64_t *ret) {
+        int r;
+
+        assert(t);
+        assert(ret);
+
+        uint64_t ss;
+
+        r = safe_atou64(t, &ss);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse sector size parameter %s", t);
+        if (ss < 512 || ss > 4096) /* Allow up to 4K due to dm-crypt support and 4K alignment by the homed LUKS backend */
+                return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Sector size not between 512 and 4096: %s", t);
+        if (!ISPOWEROF2(ss))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size not power of 2: %s", t);
+
+        *ret = ss;
+        return 0;
+}
+
 int parse_range(const char *t, unsigned *lower, unsigned *upper) {
         _cleanup_free_ char *word = NULL;
         unsigned l, u;
@@ -319,6 +339,21 @@ int parse_errno(const char *t) {
 }
 #endif /* NM_IGNORED */
 
+int parse_fd(const char *t) {
+        int r, fd;
+
+        assert(t);
+
+        r = safe_atoi(t, &fd);
+        if (r < 0)
+                return r;
+
+        if (fd < 0)
+                return -EBADF;
+
+        return fd;
+}
+
 static const char *mangle_base(const char *s, unsigned *base) {
         const char *k;
 
diff --git a/src/libnm-systemd-shared/src/basic/parse-util.h b/src/libnm-systemd-shared/src/basic/parse-util.h
index 8d8d5232..c480407c 100644
--- a/src/libnm-systemd-shared/src/basic/parse-util.h
+++ b/src/libnm-systemd-shared/src/basic/parse-util.h
@@ -18,8 +18,10 @@ int parse_ifindex(const char *s);
 int parse_mtu(int family, const char *s, uint32_t *ret);
 
 int parse_size(const char *t, uint64_t base, uint64_t *size);
+int parse_sector_size(const char *t, uint64_t *ret);
 int parse_range(const char *t, unsigned *lower, unsigned *upper);
 int parse_errno(const char *t);
+int parse_fd(const char *t);
 
 #define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30)
 #define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29)
diff --git a/src/libnm-systemd-shared/src/basic/path-util.c b/src/libnm-systemd-shared/src/basic/path-util.c
index b45bb0b5..a2af9e0c 100644
--- a/src/libnm-systemd-shared/src/basic/path-util.c
+++ b/src/libnm-systemd-shared/src/basic/path-util.c
@@ -10,7 +10,7 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "extract-word.h"
 #include "fd-util.h"
 #include "fs-util.h"
@@ -25,7 +25,7 @@
 
 #if 0 /* NM_IGNORED */
 int path_split_and_make_absolute(const char *p, char ***ret) {
-        char **l;
+        _cleanup_strv_free_ char **l = NULL;
         int r;
 
         assert(p);
@@ -36,12 +36,10 @@ int path_split_and_make_absolute(const char *p, char ***ret) {
                 return -ENOMEM;
 
         r = path_strv_make_absolute_cwd(l);
-        if (r < 0) {
-                strv_free(l);
+        if (r < 0)
                 return r;
-        }
 
-        *ret = l;
+        *ret = TAKE_PTR(l);
         return r;
 }
 
@@ -288,7 +286,7 @@ char **path_strv_resolve(char **l, const char *root) {
                 } else
                         t = *s;
 
-                r = chase_symlinks(t, root, 0, &u, NULL);
+                r = chase(t, root, 0, &u, NULL);
                 if (r == -ENOENT) {
                         if (root) {
                                 u = TAKE_PTR(orig);
@@ -491,29 +489,37 @@ int path_compare(const char *a, const char *b) {
         }
 }
 
-bool path_equal_or_files_same(const char *a, const char *b, int flags) {
-        return path_equal(a, b) || files_same(a, b, flags) > 0;
+bool path_equal_or_inode_same(const char *a, const char *b, int flags) {
+        return path_equal(a, b) || inode_same(a, b, flags) > 0;
 }
 
-bool path_equal_filename(const char *a, const char *b) {
-        _cleanup_free_ char *a_basename = NULL, *b_basename = NULL;
-        int r;
+int path_compare_filename(const char *a, const char *b) {
+        _cleanup_free_ char *fa = NULL, *fb = NULL;
+        int r, j, k;
 
-        assert(a);
-        assert(b);
+        /* Order NULL before non-NULL */
+        r = CMP(!!a, !!b);
+        if (r != 0)
+                return r;
 
-        r = path_extract_filename(a, &a_basename);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to parse basename of %s: %m", a);
-                return false;
-        }
-        r = path_extract_filename(b, &b_basename);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to parse basename of %s: %m", b);
-                return false;
-        }
+        j = path_extract_filename(a, &fa);
+        k = path_extract_filename(b, &fb);
+
+        /* When one of paths is "." or root, then order it earlier. */
+        r = CMP(j != -EADDRNOTAVAIL, k != -EADDRNOTAVAIL);
+        if (r != 0)
+                return r;
+
+        /* When one of paths is invalid (or we get OOM), order invalid path after valid one. */
+        r = CMP(j < 0, k < 0);
+        if (r != 0)
+                return r;
 
-        return path_equal(a_basename, b_basename);
+        /* fallback to use strcmp() if both paths are invalid. */
+        if (j < 0)
+                return strcmp(a, b);
+
+        return strcmp(fa, fb);
 }
 
 char* path_extend_internal(char **x, ...) {
@@ -523,17 +529,17 @@ char* path_extend_internal(char **x, ...) {
         va_list ap;
         bool slash;
 
-        /* Joins all listed strings until the sentinel and places a "/" between them unless the strings end/begin
-         * already with one so that it is unnecessary. Note that slashes which are already duplicate won't be
-         * removed. The string returned is hence always equal to or longer than the sum of the lengths of each
-         * individual string.
+        /* Joins all listed strings until the sentinel and places a "/" between them unless the strings
+         * end/begin already with one so that it is unnecessary. Note that slashes which are already
+         * duplicate won't be removed. The string returned is hence always equal to or longer than the sum of
+         * the lengths of the individual strings.
          *
          * The first argument may be an already allocated string that is extended via realloc() if
          * non-NULL. path_extend() and path_join() are macro wrappers around this function, making use of the
          * first parameter to distinguish the two operations.
          *
-         * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of which some
-         * are optional.
+         * Note: any listed empty string is simply skipped. This can be useful for concatenating strings of
+         * which some are optional.
          *
          * Examples:
          *
@@ -592,7 +598,7 @@ char* path_extend_internal(char **x, ...) {
 
 #if 0 /* NM_IGNORED */
 static int check_x_access(const char *path, int *ret_fd) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         int r;
 
         /* We need to use O_PATH because there may be executables for which we have only exec
@@ -620,22 +626,19 @@ static int check_x_access(const char *path, int *ret_fd) {
 }
 
 static int find_executable_impl(const char *name, const char *root, char **ret_filename, int *ret_fd) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         _cleanup_free_ char *path_name = NULL;
         int r;
 
         assert(name);
 
-        /* Function chase_symlinks() is invoked only when root is not NULL, as using it regardless of
+        /* Function chase() is invoked only when root is not NULL, as using it regardless of
          * root value would alter the behavior of existing callers for example: /bin/sleep would become
          * /usr/bin/sleep when find_executables is called. Hence, this function should be invoked when
          * needed to avoid unforeseen regression or other complicated changes. */
         if (root) {
-                r = chase_symlinks(name,
-                                   root,
-                                   CHASE_PREFIX_ROOT,
-                                   &path_name,
-                                   /* ret_fd= */ NULL); /* prefix root to name in case full paths are not specified */
+                 /* prefix root to name in case full paths are not specified */
+                r = chase(name, root, CHASE_PREFIX_ROOT, &path_name, /* ret_fd= */ NULL);
                 if (r < 0)
                         return r;
 
@@ -902,6 +905,8 @@ static const char *skip_slash_or_dot_backward(const char *path, const char *q) {
                         continue;
                 if (q > path && strneq(q - 1, "/.", 2))
                         continue;
+                if (q == path && *q == '.')
+                        continue;
                 break;
         }
         return q;
@@ -926,6 +931,12 @@ int path_find_last_component(const char *path, bool accept_dot_dot, const char *
         *           ret: "bbbbb/cc//././"
         *           return value: 5 (== strlen("bbbbb"))
         *
+        *   Input:  path: "//.//aaa///bbbbb/cc//././"
+        *           next: "///bbbbb/cc//././"
+        *   Output: next: "//.//aaa///bbbbb/cc//././" (next == path)
+        *           ret: "aaa///bbbbb/cc//././"
+        *           return value: 3 (== strlen("aaa"))
+        *
         *   Input:  path: "/", ".", "", or NULL
         *   Output: next: equivalent to path
         *           ret: NULL
@@ -1171,31 +1182,35 @@ bool path_is_normalized(const char *p) {
 }
 
 #if 0 /* NM_IGNORED */
-char *file_in_same_dir(const char *path, const char *filename) {
-        char *e, *ret;
-        size_t k;
+int file_in_same_dir(const char *path, const char *filename, char **ret) {
+        _cleanup_free_ char *b = NULL;
+        int r;
 
         assert(path);
         assert(filename);
+        assert(ret);
 
-        /* This removes the last component of path and appends
-         * filename, unless the latter is absolute anyway or the
-         * former isn't */
+        /* This removes the last component of path and appends filename, unless the latter is absolute anyway
+         * or the former isn't */
 
         if (path_is_absolute(filename))
-                return strdup(filename);
-
-        e = strrchr(path, '/');
-        if (!e)
-                return strdup(filename);
+                b = strdup(filename);
+        else {
+                _cleanup_free_ char *dn = NULL;
 
-        k = strlen(filename);
-        ret = new(char, (e + 1 - path) + k + 1);
-        if (!ret)
-                return NULL;
+                r = path_extract_directory(path, &dn);
+                if (r == -EDESTADDRREQ) /* no path prefix */
+                        b = strdup(filename);
+                else if (r < 0)
+                        return r;
+                else
+                        b = path_join(dn, filename);
+        }
+        if (!b)
+                return -ENOMEM;
 
-        memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1);
-        return ret;
+        *ret = TAKE_PTR(b);
+        return 0;
 }
 
 bool hidden_or_backup_file(const char *filename) {
diff --git a/src/libnm-systemd-shared/src/basic/path-util.h b/src/libnm-systemd-shared/src/basic/path-util.h
index 949fdc86..fee6e8ee 100644
--- a/src/libnm-systemd-shared/src/basic/path-util.h
+++ b/src/libnm-systemd-shared/src/basic/path-util.h
@@ -68,15 +68,18 @@ char *path_startswith_full(const char *path, const char *prefix, bool accept_dot
 static inline char* path_startswith(const char *path, const char *prefix) {
         return path_startswith_full(path, prefix, true);
 }
-int path_compare(const char *a, const char *b) _pure_;
 
+int path_compare(const char *a, const char *b) _pure_;
 static inline bool path_equal(const char *a, const char *b) {
         return path_compare(a, b) == 0;
 }
 
-bool path_equal_or_files_same(const char *a, const char *b, int flags);
-/* Compares only the last portion of the input paths, ie: the filenames */
-bool path_equal_filename(const char *a, const char *b);
+int path_compare_filename(const char *a, const char *b);
+static inline bool path_equal_filename(const char *a, const char *b) {
+        return path_compare_filename(a, b) == 0;
+}
+
+bool path_equal_or_inode_same(const char *a, const char *b, int flags);
 
 char* path_extend_internal(char **x, ...);
 #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX)
@@ -132,6 +135,7 @@ int fsck_exists_for_fstype(const char *fstype);
 
 /* Similar to path_join(), but only works for two components, and only the first one may be NULL and returns
  * an alloca() buffer, or possibly a const pointer into the path parameter. */
+/* DEPRECATED: use path_join() instead */
 #define prefix_roota(root, path)                                        \
         ({                                                              \
                 const char* _path = (path), *_root = (root), *_ret;     \
@@ -171,7 +175,7 @@ static inline bool path_is_safe(const char *p) {
 }
 bool path_is_normalized(const char *p) _pure_;
 
-char *file_in_same_dir(const char *path, const char *filename);
+int file_in_same_dir(const char *path, const char *filename, char **ret);
 
 bool hidden_or_backup_file(const char *filename) _pure_;
 
diff --git a/src/libnm-systemd-shared/src/basic/prioq.c b/src/libnm-systemd-shared/src/basic/prioq.c
index 2f8bdf0f..0af84bd2 100644
--- a/src/libnm-systemd-shared/src/basic/prioq.c
+++ b/src/libnm-systemd-shared/src/basic/prioq.c
@@ -255,7 +255,7 @@ int prioq_remove(Prioq *q, void *data, unsigned *idx) {
         return 1;
 }
 
-int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
+void prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
         struct prioq_item *i;
         unsigned k;
 
@@ -263,12 +263,11 @@ int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
 
         i = find_item(q, data, idx);
         if (!i)
-                return 0;
+                return;
 
         k = i - q->items;
         k = shuffle_down(q, k);
         shuffle_up(q, k);
-        return 1;
 }
 
 void *prioq_peek_by_index(Prioq *q, unsigned idx) {
diff --git a/src/libnm-systemd-shared/src/basic/prioq.h b/src/libnm-systemd-shared/src/basic/prioq.h
index 508db880..f66562f3 100644
--- a/src/libnm-systemd-shared/src/basic/prioq.h
+++ b/src/libnm-systemd-shared/src/basic/prioq.h
@@ -18,7 +18,7 @@ int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func);
 int prioq_put(Prioq *q, void *data, unsigned *idx);
 int prioq_ensure_put(Prioq **q, compare_func_t compare_func, void *data, unsigned *idx);
 int prioq_remove(Prioq *q, void *data, unsigned *idx);
-int prioq_reshuffle(Prioq *q, void *data, unsigned *idx);
+void prioq_reshuffle(Prioq *q, void *data, unsigned *idx);
 
 void *prioq_peek_by_index(Prioq *q, unsigned idx) _pure_;
 static inline void *prioq_peek(Prioq *q) {
diff --git a/src/libnm-systemd-shared/src/basic/process-util.c b/src/libnm-systemd-shared/src/basic/process-util.c
index 428323ef..8601e0da 100644
--- a/src/libnm-systemd-shared/src/basic/process-util.c
+++ b/src/libnm-systemd-shared/src/basic/process-util.c
@@ -10,7 +10,6 @@
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <sys/mman.h>
 #include <sys/mount.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
@@ -26,19 +25,26 @@
 
 #include "alloc-util.h"
 #include "architecture.h"
+#include "argv-util.h"
+#include "env-file.h"
 #include "env-util.h"
 #include "errno-util.h"
 #include "escape.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
+#include "hostname-util.h"
 #include "locale-util.h"
 #include "log.h"
 #include "macro.h"
 #include "memory-util.h"
 #include "missing_sched.h"
 #include "missing_syscall.h"
+#include "missing_threads.h"
+#include "mountpoint-util.h"
 #include "namespace-util.h"
+#include "nulstr-util.h"
+#include "parse-util.h"
 #include "path-util.h"
 #include "process-util.h"
 #include "raw-clone.h"
@@ -222,18 +228,12 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
 
                 _cleanup_strv_free_ char **args = NULL;
 
-                args = strv_parse_nulstr(t, k);
+                /* Drop trailing NULs, otherwise strv_parse_nulstr() adds additional empty strings at the end.
+                 * See also issue #21186. */
+                args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
                 if (!args)
                         return -ENOMEM;
 
-                /* Drop trailing empty strings. See issue #21186. */
-                STRV_FOREACH_BACKWARDS(p, args) {
-                        if (!isempty(*p))
-                                break;
-
-                        *p = mfree(*p);
-                }
-
                 ans = quote_command_line(args, shflags);
                 if (!ans)
                         return -ENOMEM;
@@ -259,149 +259,67 @@ int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags
         return 0;
 }
 
-static int update_argv(const char name[], size_t l) {
-        static int can_do = -1;
-
-        if (can_do == 0)
-                return 0;
-        can_do = false; /* We'll set it to true only if the whole process works */
-
-        /* Let's not bother with this if we don't have euid == 0. Strictly speaking we should check for the
-         * CAP_SYS_RESOURCE capability which is independent of the euid. In our own code the capability generally is
-         * present only for euid == 0, hence let's use this as quick bypass check, to avoid calling mmap() if
-         * PR_SET_MM_ARG_{START,END} fails with EPERM later on anyway. After all geteuid() is dead cheap to call, but
-         * mmap() is not. */
-        if (geteuid() != 0)
-                return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
-                                       "Skipping PR_SET_MM, as we don't have privileges.");
-
-        static size_t mm_size = 0;
-        static char *mm = NULL;
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret) {
+        _cleanup_free_ char *t = NULL;
+        char **args;
+        size_t k;
         int r;
 
-        if (mm_size < l+1) {
-                size_t nn_size;
-                char *nn;
-
-                nn_size = PAGE_ALIGN(l+1);
-                nn = mmap(NULL, nn_size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
-                if (nn == MAP_FAILED)
-                        return log_debug_errno(errno, "mmap() failed: %m");
-
-                strncpy(nn, name, nn_size);
-
-                /* Now, let's tell the kernel about this new memory */
-                if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0) {
-                        if (ERRNO_IS_PRIVILEGE(errno))
-                                return log_debug_errno(errno, "PR_SET_MM_ARG_START failed: %m");
-
-                        /* HACK: prctl() API is kind of dumb on this point.  The existing end address may already be
-                         * below the desired start address, in which case the kernel may have kicked this back due
-                         * to a range-check failure (see linux/kernel/sys.c:validate_prctl_map() to see this in
-                         * action).  The proper solution would be to have a prctl() API that could set both start+end
-                         * simultaneously, or at least let us query the existing address to anticipate this condition
-                         * and respond accordingly.  For now, we can only guess at the cause of this failure and try
-                         * a workaround--which will briefly expand the arg space to something potentially huge before
-                         * resizing it to what we want. */
-                        log_debug_errno(errno, "PR_SET_MM_ARG_START failed, attempting PR_SET_MM_ARG_END hack: %m");
-
-                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0) {
-                                r = log_debug_errno(errno, "PR_SET_MM_ARG_END hack failed, proceeding without: %m");
-                                (void) munmap(nn, nn_size);
-                                return r;
-                        }
-
-                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_START, (unsigned long) nn, 0, 0) < 0)
-                                return log_debug_errno(errno, "PR_SET_MM_ARG_START still failed, proceeding without: %m");
-                } else {
-                        /* And update the end pointer to the new end, too. If this fails, we don't really know what
-                         * to do, it's pretty unlikely that we can rollback, hence we'll just accept the failure,
-                         * and continue. */
-                        if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) nn + l + 1, 0, 0) < 0)
-                                log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
-                }
-
-                if (mm)
-                        (void) munmap(mm, mm_size);
+        assert(pid >= 0);
+        assert((flags & ~PROCESS_CMDLINE_COMM_FALLBACK) == 0);
+        assert(ret);
 
-                mm = nn;
-                mm_size = nn_size;
-        } else {
-                strncpy(mm, name, mm_size);
+        r = get_process_cmdline_nulstr(pid, SIZE_MAX, flags, &t, &k);
+        if (r < 0)
+                return r;
 
-                /* Update the end pointer, continuing regardless of any failure. */
-                if (prctl(PR_SET_MM, PR_SET_MM_ARG_END, (unsigned long) mm + l + 1, 0, 0) < 0)
-                        log_debug_errno(errno, "PR_SET_MM_ARG_END failed, proceeding without: %m");
-        }
+        args = strv_parse_nulstr_full(t, k, /* drop_trailing_nuls = */ true);
+        if (!args)
+                return -ENOMEM;
 
-        can_do = true;
+        *ret = args;
         return 0;
 }
 
-int rename_process(const char name[]) {
-        bool truncated = false;
-
-        /* This is a like a poor man's setproctitle(). It changes the comm field, argv[0], and also the glibc's
-         * internally used name of the process. For the first one a limit of 16 chars applies; to the second one in
-         * many cases one of 10 (i.e. length of "/sbin/init") — however if we have CAP_SYS_RESOURCES it is unbounded;
-         * to the third one 7 (i.e. the length of "systemd". If you pass a longer string it will likely be
-         * truncated.
-         *
-         * Returns 0 if a name was set but truncated, > 0 if it was set but not truncated. */
-
-        if (isempty(name))
-                return -EINVAL; /* let's not confuse users unnecessarily with an empty name */
-
-        if (!is_main_thread())
-                return -EPERM; /* Let's not allow setting the process name from other threads than the main one, as we
-                                * cache things without locking, and we make assumptions that PR_SET_NAME sets the
-                                * process name that isn't correct on any other threads */
-
-        size_t l = strlen(name);
-
-        /* First step, change the comm field. The main thread's comm is identical to the process comm. This means we
-         * can use PR_SET_NAME, which sets the thread name for the calling thread. */
-        if (prctl(PR_SET_NAME, name) < 0)
-                log_debug_errno(errno, "PR_SET_NAME failed: %m");
-        if (l >= TASK_COMM_LEN) /* Linux userspace process names can be 15 chars at max */
-                truncated = true;
+int container_get_leader(const char *machine, pid_t *pid) {
+        _cleanup_free_ char *s = NULL, *class = NULL;
+        const char *p;
+        pid_t leader;
+        int r;
 
-        /* Second step, change glibc's ID of the process name. */
-        if (program_invocation_name) {
-                size_t k;
+        assert(machine);
+        assert(pid);
 
-                k = strlen(program_invocation_name);
-                strncpy(program_invocation_name, name, k);
-                if (l > k)
-                        truncated = true;
+        if (streq(machine, ".host")) {
+                *pid = 1;
+                return 0;
         }
 
-        /* Third step, completely replace the argv[] array the kernel maintains for us. This requires privileges, but
-         * has the advantage that the argv[] array is exactly what we want it to be, and not filled up with zeros at
-         * the end. This is the best option for changing /proc/self/cmdline. */
-        (void) update_argv(name, l);
-
-        /* Fourth step: in all cases we'll also update the original argv[], so that our own code gets it right too if
-         * it still looks here */
-        if (saved_argc > 0) {
-                if (saved_argv[0]) {
-                        size_t k;
-
-                        k = strlen(saved_argv[0]);
-                        strncpy(saved_argv[0], name, k);
-                        if (l > k)
-                                truncated = true;
-                }
+        if (!hostname_is_valid(machine, 0))
+                return -EINVAL;
 
-                for (int i = 1; i < saved_argc; i++) {
-                        if (!saved_argv[i])
-                                break;
+        p = strjoina("/run/systemd/machines/", machine);
+        r = parse_env_file(NULL, p,
+                           "LEADER", &s,
+                           "CLASS", &class);
+        if (r == -ENOENT)
+                return -EHOSTDOWN;
+        if (r < 0)
+                return r;
+        if (!s)
+                return -EIO;
 
-                        memzero(saved_argv[i], strlen(saved_argv[i]));
-                }
-        }
+        if (!streq_ptr(class, "container"))
+                return -EIO;
 
-        return !truncated;
+        r = parse_pid(s, &leader);
+        if (r < 0)
+                return r;
+        if (leader <= 1)
+                return -EIO;
+
+        *pid = leader;
+        return 0;
 }
 
 int is_kernel_thread(pid_t pid) {
@@ -701,6 +619,8 @@ int get_process_umask(pid_t pid, mode_t *ret) {
         r = get_proc_field(p, "Umask", WHITESPACE, &m);
         if (r == -ENOENT)
                 return -ESRCH;
+        if (r < 0)
+                return r;
 
         return parse_mode(m, ret);
 }
@@ -870,6 +790,23 @@ void sigterm_wait(pid_t pid) {
         (void) wait_for_terminate(pid, NULL);
 }
 
+void sigkill_nowait(pid_t pid) {
+        assert(pid > 1);
+
+        (void) kill(pid, SIGKILL);
+}
+
+void sigkill_nowaitp(pid_t *pid) {
+        PROTECT_ERRNO;
+
+        if (!pid)
+                return;
+        if (*pid <= 1)
+                return;
+
+        sigkill_nowait(*pid);
+}
+
 int kill_and_sigcont(pid_t pid, int sig) {
         int r;
 
@@ -1015,7 +952,7 @@ int pid_from_same_root_fs(pid_t pid) {
 
         root = procfs_file_alloca(pid, "root");
 
-        return files_same(root, "/proc/1/root", 0);
+        return inode_same(root, "/proc/1/root", 0);
 }
 #endif /* NM_IGNORED */
 
@@ -1224,6 +1161,7 @@ static void restore_sigsetp(sigset_t **ssp) {
 
 int safe_fork_full(
                 const char *name,
+                const int stdio_fds[3],
                 const int except_fds[],
                 size_t n_except_fds,
                 ForkFlags flags,
@@ -1275,7 +1213,7 @@ int safe_fork_full(
         else
                 pid = fork();
         if (pid < 0)
-                return log_full_errno(prio, errno, "Failed to fork: %m");
+                return log_full_errno(prio, errno, "Failed to fork off '%s': %m", strna(name));
         if (pid > 0) {
                 /* We are in the parent process */
 
@@ -1311,6 +1249,7 @@ int safe_fork_full(
                 /* Close the logs if requested, before we log anything. And make sure we reopen it if needed. */
                 log_close();
                 log_set_open_when_needed(true);
+                log_settle_target();
         }
 
         if (name) {
@@ -1362,15 +1301,47 @@ int safe_fork_full(
         }
 
         if (FLAGS_SET(flags, FORK_NEW_MOUNTNS | FORK_MOUNTNS_SLAVE)) {
-
                 /* Optionally, make sure we never propagate mounts to the host. */
-
                 if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
                         log_full_errno(prio, errno, "Failed to remount root directory as MS_SLAVE: %m");
                         _exit(EXIT_FAILURE);
                 }
         }
 
+        if (FLAGS_SET(flags, FORK_PRIVATE_TMP)) {
+                assert(FLAGS_SET(flags, FORK_NEW_MOUNTNS));
+
+                /* Optionally, overmount new tmpfs instance on /tmp/. */
+                r = mount_nofollow("tmpfs", "/tmp", "tmpfs",
+                                   MS_NOSUID|MS_NODEV,
+                                   "mode=01777" TMPFS_LIMITS_RUN);
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to overmount /tmp/: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
+        if (flags & FORK_REARRANGE_STDIO) {
+                if (stdio_fds) {
+                        r = rearrange_stdio(stdio_fds[0], stdio_fds[1], stdio_fds[2]);
+                        if (r < 0) {
+                                log_full_errno(prio, r, "Failed to rearrange stdio fds: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+                } else {
+                        r = make_null_stdio();
+                        if (r < 0) {
+                                log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+                }
+        } else if (flags & FORK_STDOUT_TO_STDERR) {
+                if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) {
+                        log_full_errno(prio, errno, "Failed to connect stdout to stderr: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
         if (flags & FORK_CLOSE_ALL_FDS) {
                 /* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */
                 log_close();
@@ -1382,30 +1353,32 @@ int safe_fork_full(
                 }
         }
 
+        if (flags & FORK_CLOEXEC_OFF) {
+                r = fd_cloexec_many(except_fds, n_except_fds, false);
+                if (r < 0) {
+                        log_full_errno(prio, r, "Failed to turn off O_CLOEXEC on file descriptors: %m");
+                        _exit(EXIT_FAILURE);
+                }
+        }
+
         /* When we were asked to reopen the logs, do so again now */
         if (flags & FORK_REOPEN_LOG) {
                 log_open();
                 log_set_open_when_needed(false);
         }
 
-        if (flags & FORK_NULL_STDIO) {
-                r = make_null_stdio();
+        if (flags & FORK_RLIMIT_NOFILE_SAFE) {
+                r = rlimit_nofile_safe();
                 if (r < 0) {
-                        log_full_errno(prio, r, "Failed to connect stdin/stdout to /dev/null: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-        } else if (flags & FORK_STDOUT_TO_STDERR) {
-                if (dup2(STDERR_FILENO, STDOUT_FILENO) < 0) {
-                        log_full_errno(prio, errno, "Failed to connect stdout to stderr: %m");
+                        log_full_errno(prio, r, "Failed to lower RLIMIT_NOFILE's soft limit to 1K: %m");
                         _exit(EXIT_FAILURE);
                 }
         }
 
-        if (flags & FORK_RLIMIT_NOFILE_SAFE) {
-                r = rlimit_nofile_safe();
+        if (!FLAGS_SET(flags, FORK_KEEP_NOTIFY_SOCKET)) {
+                r = RET_NERRNO(unsetenv("NOTIFY_SOCKET"));
                 if (r < 0) {
-                        log_full_errno(prio, r, "Failed to lower RLIMIT_NOFILE's soft limit to 1K: %m");
+                        log_full_errno(prio, r, "Failed to unset $NOTIFY_SOCKET: %m");
                         _exit(EXIT_FAILURE);
                 }
         }
@@ -1435,7 +1408,10 @@ int namespace_fork(
          * process. This ensures that we are fully a member of the destination namespace, with pidns an all, so that
          * /proc/self/fd works correctly. */
 
-        r = safe_fork_full(outer_name, except_fds, n_except_fds, (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid);
+        r = safe_fork_full(outer_name,
+                           NULL,
+                           except_fds, n_except_fds,
+                           (flags|FORK_DEATHSIG) & ~(FORK_REOPEN_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE), ret_pid);
         if (r < 0)
                 return r;
         if (r == 0) {
@@ -1450,7 +1426,10 @@ int namespace_fork(
                 }
 
                 /* We mask a few flags here that either make no sense for the grandchild, or that we don't have to do again */
-                r = safe_fork_full(inner_name, except_fds, n_except_fds, flags & ~(FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_NULL_STDIO), &pid);
+                r = safe_fork_full(inner_name,
+                                   NULL,
+                                   except_fds, n_except_fds,
+                                   flags & ~(FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO), &pid);
                 if (r < 0)
                         _exit(EXIT_FAILURE);
                 if (r == 0) {
@@ -1503,6 +1482,15 @@ int pidfd_get_pid(int fd, pid_t *ret) {
         char *p;
         int r;
 
+        /* Converts a pidfd into a pid. Well known errors:
+         *
+         *    -EBADF   → fd invalid
+         *    -ENOSYS  → /proc/ not mounted
+         *    -ENOTTY  → fd valid, but not a pidfd
+         *    -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one
+         *    -ESRCH   → fd valid, but process is already reaped
+         */
+
         if (fd < 0)
                 return -EBADF;
 
@@ -1510,25 +1498,39 @@ int pidfd_get_pid(int fd, pid_t *ret) {
 
         r = read_full_virtual_file(path, &fdinfo, NULL);
         if (r == -ENOENT) /* if fdinfo doesn't exist we assume the process does not exist */
-                return -ESRCH;
+                return proc_mounted() > 0 ? -EBADF : -ENOSYS;
         if (r < 0)
                 return r;
 
-        p = startswith(fdinfo, "Pid:");
-        if (!p) {
-                p = strstr(fdinfo, "\nPid:");
-                if (!p)
-                        return -ENOTTY; /* not a pidfd? */
-
-                p += 5;
-        }
+        p = find_line_startswith(fdinfo, "Pid:");
+        if (!p)
+                return -ENOTTY; /* not a pidfd? */
 
         p += strspn(p, WHITESPACE);
         p[strcspn(p, WHITESPACE)] = 0;
 
+        if (streq(p, "0"))
+                return -EREMOTE; /* PID is in foreign PID namespace? */
+        if (streq(p, "-1"))
+                return -ESRCH;   /* refers to reaped process? */
+
         return parse_pid(p, ret);
 }
 
+int pidfd_verify_pid(int pidfd, pid_t pid) {
+        pid_t current_pid;
+        int r;
+
+        assert(pidfd >= 0);
+        assert(pid > 0);
+
+        r = pidfd_get_pid(pidfd, &current_pid);
+        if (r < 0)
+                return r;
+
+        return current_pid != pid ? -ESRCH : 0;
+}
+
 static int rlimit_to_nice(rlim_t limit) {
         if (limit <= 1)
                 return PRIO_MAX-1; /* i.e. 19 */
@@ -1585,40 +1587,6 @@ int setpriority_closest(int priority) {
         return 0;
 }
 
-bool invoked_as(char *argv[], const char *token) {
-        if (!argv || isempty(argv[0]))
-                return false;
-
-        if (isempty(token))
-                return false;
-
-        return strstr(last_path_component(argv[0]), token);
-}
-
-bool invoked_by_systemd(void) {
-        int r;
-
-        /* If the process is directly executed by PID1 (e.g. ExecStart= or generator), systemd-importd,
-         * or systemd-homed, then $SYSTEMD_EXEC_PID= is set, and read the command line. */
-        const char *e = getenv("SYSTEMD_EXEC_PID");
-        if (!e)
-                return false;
-
-        if (streq(e, "*"))
-                /* For testing. */
-                return true;
-
-        pid_t p;
-        r = parse_pid(e, &p);
-        if (r < 0) {
-                /* We know that systemd sets the variable correctly. Something else must have set it. */
-                log_debug_errno(r, "Failed to parse \"SYSTEMD_EXEC_PID=%s\", ignoring: %m", e);
-                return false;
-        }
-
-        return getpid_cached() == p;
-}
-
 _noreturn_ void freeze(void) {
         log_close();
 
@@ -1640,29 +1608,29 @@ _noreturn_ void freeze(void) {
                 pause();
 }
 
-bool argv_looks_like_help(int argc, char **argv) {
-        char **l;
+int get_process_threads(pid_t pid) {
+        _cleanup_free_ char *t = NULL;
+        const char *p;
+        int n, r;
 
-        /* Scans the command line for indications the user asks for help. This is supposed to be called by
-         * tools that do not implement getopt() style command line parsing because they are not primarily
-         * user-facing. Detects four ways of asking for help:
-         *
-         * 1. Passing zero arguments
-         * 2. Passing "help" as first argument
-         * 3. Passing --help as any argument
-         * 4. Passing -h as any argument
-         */
+        if (pid < 0)
+                return -EINVAL;
 
-        if (argc <= 1)
-                return true;
+        p = procfs_file_alloca(pid, "status");
 
-        if (streq_ptr(argv[1], "help"))
-                return true;
+        r = get_proc_field(p, "Threads", WHITESPACE, &t);
+        if (r == -ENOENT)
+                return proc_mounted() == 0 ? -ENOSYS : -ESRCH;
+        if (r < 0)
+                return r;
 
-        l = strv_skip(argv, 1);
+        r = safe_atoi(t, &n);
+        if (r < 0)
+                return r;
+        if (n < 0)
+                return -EINVAL;
 
-        return strv_contains(l, "--help") ||
-                strv_contains(l, "-h");
+        return n;
 }
 
 static const char *const sigchld_code_table[] = {
diff --git a/src/libnm-systemd-shared/src/basic/process-util.h b/src/libnm-systemd-shared/src/basic/process-util.h
index 962f12d1..5cf5c7c6 100644
--- a/src/libnm-systemd-shared/src/basic/process-util.h
+++ b/src/libnm-systemd-shared/src/basic/process-util.h
@@ -40,6 +40,7 @@ typedef enum ProcessCmdlineFlags {
 
 int get_process_comm(pid_t pid, char **ret);
 int get_process_cmdline(pid_t pid, size_t max_columns, ProcessCmdlineFlags flags, char **ret);
+int get_process_cmdline_strv(pid_t pid, ProcessCmdlineFlags flags, char ***ret);
 int get_process_exe(pid_t pid, char **ret);
 int get_process_uid(pid_t pid, uid_t *ret);
 int get_process_gid(pid_t pid, gid_t *ret);
@@ -50,6 +51,8 @@ int get_process_environ(pid_t pid, char **ret);
 int get_process_ppid(pid_t pid, pid_t *ret);
 int get_process_umask(pid_t pid, mode_t *ret);
 
+int container_get_leader(const char *machine, pid_t *pid);
+
 int wait_for_terminate(pid_t pid, siginfo_t *status);
 
 typedef enum WaitFlags {
@@ -66,10 +69,11 @@ int wait_for_terminate_with_timeout(pid_t pid, usec_t timeout);
 void sigkill_wait(pid_t pid);
 void sigkill_waitp(pid_t *pid);
 void sigterm_wait(pid_t pid);
+void sigkill_nowait(pid_t pid);
+void sigkill_nowaitp(pid_t *pid);
 
 int kill_and_sigcont(pid_t pid, int sig);
 
-int rename_process(const char name[]);
 int is_kernel_thread(pid_t pid);
 
 int getenv_for_pid(pid_t pid, const char *field, char **_value);
@@ -142,22 +146,31 @@ typedef enum ForkFlags {
         FORK_CLOSE_ALL_FDS      = 1 <<  1, /* Close all open file descriptors in the child, except for 0,1,2 */
         FORK_DEATHSIG           = 1 <<  2, /* Set PR_DEATHSIG in the child to SIGTERM */
         FORK_DEATHSIG_SIGINT    = 1 <<  3, /* Set PR_DEATHSIG in the child to SIGINT */
-        FORK_NULL_STDIO         = 1 <<  4, /* Connect 0,1,2 to /dev/null */
+        FORK_REARRANGE_STDIO    = 1 <<  4, /* Connect 0,1,2 to specified fds or /dev/null */
         FORK_REOPEN_LOG         = 1 <<  5, /* Reopen log connection */
         FORK_LOG                = 1 <<  6, /* Log above LOG_DEBUG log level about failures */
         FORK_WAIT               = 1 <<  7, /* Wait until child exited */
         FORK_NEW_MOUNTNS        = 1 <<  8, /* Run child in its own mount namespace */
         FORK_MOUNTNS_SLAVE      = 1 <<  9, /* Make child's mount namespace MS_SLAVE */
-        FORK_RLIMIT_NOFILE_SAFE = 1 << 10, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
-        FORK_STDOUT_TO_STDERR   = 1 << 11, /* Make stdout a copy of stderr */
-        FORK_FLUSH_STDIO        = 1 << 12, /* fflush() stdout (and stderr) before forking */
-        FORK_NEW_USERNS         = 1 << 13, /* Run child in its own user namespace */
+        FORK_PRIVATE_TMP        = 1 << 10, /* Mount new /tmp/ in the child (combine with FORK_NEW_MOUNTNS!) */
+        FORK_RLIMIT_NOFILE_SAFE = 1 << 11, /* Set RLIMIT_NOFILE soft limit to 1K for select() compat */
+        FORK_STDOUT_TO_STDERR   = 1 << 12, /* Make stdout a copy of stderr */
+        FORK_FLUSH_STDIO        = 1 << 13, /* fflush() stdout (and stderr) before forking */
+        FORK_NEW_USERNS         = 1 << 14, /* Run child in its own user namespace */
+        FORK_CLOEXEC_OFF        = 1 << 15, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */
+        FORK_KEEP_NOTIFY_SOCKET = 1 << 16, /* Unless this specified, $NOTIFY_SOCKET will be unset. */
 } ForkFlags;
 
-int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
+int safe_fork_full(
+                const char *name,
+                const int stdio_fds[3],
+                const int except_fds[],
+                size_t n_except_fds,
+                ForkFlags flags,
+                pid_t *ret_pid);
 
 static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) {
-        return safe_fork_full(name, NULL, 0, flags, ret_pid);
+        return safe_fork_full(name, NULL, NULL, 0, flags, ret_pid);
 }
 
 int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid);
@@ -177,23 +190,14 @@ int get_oom_score_adjust(int *ret);
 
 assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX);
 
-/* Like TAKE_PTR() but for child PIDs, resetting them to 0 */
-#define TAKE_PID(pid)                           \
-        ({                                      \
-                pid_t *_ppid_ = &(pid);         \
-                pid_t _pid_ = *_ppid_;          \
-                *_ppid_ = 0;                    \
-                _pid_;                          \
-        })
+/* Like TAKE_PTR() but for pid_t, resetting them to 0 */
+#define TAKE_PID(pid) TAKE_GENERIC(pid, pid_t, 0)
 
 int pidfd_get_pid(int fd, pid_t *ret);
+int pidfd_verify_pid(int pidfd, pid_t pid);
 
 int setpriority_closest(int priority);
 
-bool invoked_as(char *argv[], const char *token);
-
-bool invoked_by_systemd(void);
-
 _noreturn_ void freeze(void);
 
-bool argv_looks_like_help(int argc, char **argv);
+int get_process_threads(pid_t pid);
diff --git a/src/libnm-systemd-shared/src/basic/random-util.c b/src/libnm-systemd-shared/src/basic/random-util.c
index 9e1a746f..934d5e25 100644
--- a/src/libnm-systemd-shared/src/basic/random-util.c
+++ b/src/libnm-systemd-shared/src/basic/random-util.c
@@ -26,6 +26,7 @@
 #include "io-util.h"
 #include "missing_random.h"
 #include "missing_syscall.h"
+#include "missing_threads.h"
 #include "parse-util.h"
 #include "random-util.h"
 #include "sha256.h"
@@ -77,7 +78,7 @@ static void fallback_random_bytes(void *p, size_t n) {
 
 void random_bytes(void *p, size_t n) {
         static bool have_getrandom = true, have_grndinsecure = true;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         if (n == 0)
                 return;
@@ -119,7 +120,7 @@ void random_bytes(void *p, size_t n) {
 
 int crypto_random_bytes(void *p, size_t n) {
         static bool have_getrandom = true, seen_initialized = false;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         if (n == 0)
                 return 0;
@@ -147,7 +148,7 @@ int crypto_random_bytes(void *p, size_t n) {
         }
 
         if (!seen_initialized) {
-                _cleanup_close_ int ready_fd = -1;
+                _cleanup_close_ int ready_fd = -EBADF;
                 int r;
 
                 ready_fd = open("/dev/random", O_RDONLY|O_CLOEXEC|O_NOCTTY);
@@ -190,7 +191,7 @@ size_t random_pool_size(void) {
 }
 
 int random_write_entropy(int fd, const void *seed, size_t size, bool credit) {
-        _cleanup_close_ int opened_fd = -1;
+        _cleanup_close_ int opened_fd = -EBADF;
         int r;
 
         assert(seed || size == 0);
diff --git a/src/libnm-systemd-shared/src/basic/random-util.h b/src/libnm-systemd-shared/src/basic/random-util.h
index 2d998072..b1a4d109 100644
--- a/src/libnm-systemd-shared/src/basic/random-util.h
+++ b/src/libnm-systemd-shared/src/basic/random-util.h
@@ -23,6 +23,7 @@ static inline uint32_t random_u32(void) {
 /* Some limits on the pool sizes when we deal with the kernel random pool */
 #define RANDOM_POOL_SIZE_MIN 32U
 #define RANDOM_POOL_SIZE_MAX (10U*1024U*1024U)
+#define RANDOM_EFI_SEED_SIZE 32U
 
 size_t random_pool_size(void);
 
diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.c b/src/libnm-systemd-shared/src/basic/ratelimit.c
index 3ccec119..a28c8122 100644
--- a/src/libnm-systemd-shared/src/basic/ratelimit.c
+++ b/src/libnm-systemd-shared/src/basic/ratelimit.c
@@ -22,26 +22,41 @@ bool ratelimit_below(RateLimit *r) {
 
         if (r->begin <= 0 ||
             usec_sub_unsigned(ts, r->begin) > r->interval) {
-                r->begin = ts;
-
-                /* Reset counter */
-                r->num = 0;
-                goto good;
+                r->begin = ts;  /* Start a new time window */
+                r->num = 1;     /* Reset counter */
+                return true;
         }
 
-        if (r->num < r->burst)
-                goto good;
-
-        r->num++;
-        return false;
+        if (_unlikely_(r->num == UINT_MAX))
+                return false;
 
-good:
         r->num++;
-        return true;
+        return r->num <= r->burst;
 }
 
 unsigned ratelimit_num_dropped(RateLimit *r) {
         assert(r);
 
-        return r->num > r->burst ? r->num - r->burst : 0;
+        if (r->num == UINT_MAX) /* overflow, return as special case */
+                return UINT_MAX;
+
+        return LESS_BY(r->num, r->burst);
+}
+
+usec_t ratelimit_end(const RateLimit *rl) {
+        assert(rl);
+
+        if (rl->begin == 0)
+                return 0;
+
+        return usec_add(rl->begin, rl->interval);
+}
+
+usec_t ratelimit_left(const RateLimit *rl) {
+        assert(rl);
+
+        if (rl->begin == 0)
+                return 0;
+
+        return usec_sub_unsigned(ratelimit_end(rl), now(CLOCK_MONOTONIC));
 }
diff --git a/src/libnm-systemd-shared/src/basic/ratelimit.h b/src/libnm-systemd-shared/src/basic/ratelimit.h
index 22361898..bb7160a8 100644
--- a/src/libnm-systemd-shared/src/basic/ratelimit.h
+++ b/src/libnm-systemd-shared/src/basic/ratelimit.h
@@ -23,3 +23,6 @@ static inline bool ratelimit_configured(RateLimit *rl) {
 bool ratelimit_below(RateLimit *r);
 
 unsigned ratelimit_num_dropped(RateLimit *r);
+
+usec_t ratelimit_end(const RateLimit *rl);
+usec_t ratelimit_left(const RateLimit *rl);
diff --git a/src/libnm-systemd-shared/src/basic/signal-util.c b/src/libnm-systemd-shared/src/basic/signal-util.c
index 6d8c9920..270d397d 100644
--- a/src/libnm-systemd-shared/src/basic/signal-util.c
+++ b/src/libnm-systemd-shared/src/basic/signal-util.c
@@ -7,6 +7,8 @@
 
 #include "errno-util.h"
 #include "macro.h"
+#include "missing_syscall.h"
+#include "missing_threads.h"
 #include "parse-util.h"
 #include "signal-util.h"
 #include "stdio-util.h"
@@ -285,4 +287,21 @@ int pop_pending_signal_internal(int sig, ...) {
 
         return r; /* Returns the signal popped */
 }
+
+void propagate_signal(int sig, siginfo_t *siginfo) {
+        pid_t p;
+
+        /* To be called from a signal handler. Will raise the same signal again, in our process + in our threads.
+         *
+         * Note that we use raw_getpid() instead of getpid_cached(). We might have forked with raw_clone()
+         * earlier (see PID 1), and hence let's go to the raw syscall here. In particular as this is not
+         * performance sensitive code.
+         *
+         * Note that we use kill() rather than raise() as fallback, for similar reasons. */
+
+        p = raw_getpid();
+
+        if (rt_tgsigqueueinfo(p, gettid(), sig, siginfo) < 0)
+                assert_se(kill(p, sig) >= 0);
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/signal-util.h b/src/libnm-systemd-shared/src/basic/signal-util.h
index 36372c19..ad2ba841 100644
--- a/src/libnm-systemd-shared/src/basic/signal-util.h
+++ b/src/libnm-systemd-shared/src/basic/signal-util.h
@@ -65,3 +65,5 @@ int signal_is_blocked(int sig);
 
 int pop_pending_signal_internal(int sig, ...);
 #define pop_pending_signal(...) pop_pending_signal_internal(__VA_ARGS__, -1)
+
+void propagate_signal(int sig, siginfo_t *siginfo);
diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c
index c155e352..9b411e07 100644
--- a/src/libnm-systemd-shared/src/basic/socket-util.c
+++ b/src/libnm-systemd-shared/src/basic/socket-util.c
@@ -228,7 +228,7 @@ bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) {
                         return false;
 
                 if (a->sockaddr.un.sun_path[0]) {
-                        if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0))
+                        if (!path_equal_or_inode_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, 0))
                                 return false;
                 } else {
                         if (a->size != b->size)
@@ -1054,9 +1054,9 @@ ssize_t receive_one_fd_iov(
         }
 
         if (found)
-                *ret_fd = *(int*) CMSG_DATA(found);
+                *ret_fd = *CMSG_TYPED_DATA(found, int);
         else
-                *ret_fd = -1;
+                *ret_fd = -EBADF;
 
         return k;
 }
@@ -1181,6 +1181,24 @@ struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t leng
         return NULL;
 }
 
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len) {
+        struct cmsghdr *cmsg;
+
+        assert(mh);
+        assert(buf);
+        assert(buf_len > 0);
+
+        /* This is similar to cmsg_find_data(), but copy the found data to buf. This should be typically used
+         * when reading possibly unaligned data such as timestamp, as time_t is 64bit and size_t is 32bit on
+         * RISCV32. See issue #27241. */
+
+        cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len));
+        if (!cmsg)
+                return NULL;
+
+        return memcpy_safe(buf, CMSG_DATA(cmsg), buf_len);
+}
+
 #if 0 /* NM_IGNORED */
 int socket_ioctl_fd(void) {
         int fd;
@@ -1320,7 +1338,7 @@ ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags) {
 }
 
 #if 0 /* NM_IGNORED */
-int socket_get_family(int fd, int *ret) {
+int socket_get_family(int fd) {
         int af;
         socklen_t sl = sizeof(af);
 
@@ -1334,12 +1352,11 @@ int socket_get_family(int fd, int *ret) {
 }
 
 int socket_set_recvpktinfo(int fd, int af, bool b) {
-        int r;
 
         if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
+                af = socket_get_family(fd);
+                if (af < 0)
+                        return af;
         }
 
         switch (af) {
@@ -1363,12 +1380,11 @@ int socket_set_recvpktinfo(int fd, int af, bool b) {
 
 int socket_set_unicast_if(int fd, int af, int ifi) {
         be32_t ifindex_be = htobe32(ifi);
-        int r;
 
         if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
+                af = socket_get_family(fd);
+                if (af < 0)
+                        return af;
         }
 
         switch (af) {
@@ -1385,12 +1401,10 @@ int socket_set_unicast_if(int fd, int af, int ifi) {
 }
 
 int socket_set_option(int fd, int af, int opt_ipv4, int opt_ipv6, int val) {
-        int r;
-
         if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
+                af = socket_get_family(fd);
+                if (af < 0)
+                        return af;
         }
 
         switch (af) {
@@ -1410,9 +1424,9 @@ int socket_get_mtu(int fd, int af, size_t *ret) {
         int mtu, r;
 
         if (af == AF_UNSPEC) {
-                r = socket_get_family(fd, &af);
-                if (r < 0)
-                        return r;
+                af = socket_get_family(fd);
+                if (af < 0)
+                        return af;
         }
 
         switch (af) {
@@ -1439,50 +1453,136 @@ int socket_get_mtu(int fd, int af, size_t *ret) {
 }
 #endif /* NM_IGNORED */
 
-int connect_unix_path(int fd, int dir_fd, const char *path) {
-        _cleanup_close_ int inode_fd = -1;
+static int connect_unix_path_simple(int fd, const char *path) {
         union sockaddr_union sa = {
                 .un.sun_family = AF_UNIX,
         };
-        size_t path_len;
-        socklen_t salen;
+        size_t l;
 
         assert(fd >= 0);
-        assert(dir_fd == AT_FDCWD || dir_fd >= 0);
         assert(path);
 
+        l = strlen(path);
+        assert(l > 0);
+        assert(l < sizeof(sa.un.sun_path));
+
+        memcpy(sa.un.sun_path, path, l + 1);
+        return RET_NERRNO(connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + l + 1));
+}
+
+static int connect_unix_inode(int fd, int inode_fd) {
+        assert(fd >= 0);
+        assert(inode_fd >= 0);
+
+        return connect_unix_path_simple(fd, FORMAT_PROC_FD_PATH(inode_fd));
+}
+
+int connect_unix_path(int fd, int dir_fd, const char *path) {
+        _cleanup_close_ int inode_fd = -EBADF;
+
+        assert(fd >= 0);
+        assert(dir_fd == AT_FDCWD || dir_fd >= 0);
+
         /* Connects to the specified AF_UNIX socket in the file system. Works around the 108 byte size limit
          * in sockaddr_un, by going via O_PATH if needed. This hence works for any kind of path. */
 
-        path_len = strlen(path);
+        if (!path)
+                return connect_unix_inode(fd, dir_fd); /* If no path is specified, then dir_fd refers to the socket inode to connect to. */
 
         /* Refuse zero length path early, to make sure AF_UNIX stack won't mistake this for an abstract
          * namespace path, since first char is NUL */
-        if (path_len <= 0)
+        if (isempty(path))
                 return -EINVAL;
 
-        if (dir_fd == AT_FDCWD && path_len < sizeof(sa.un.sun_path)) {
-                memcpy(sa.un.sun_path, path, path_len + 1);
-                salen = offsetof(struct sockaddr_un, sun_path) + path_len + 1;
-        } else {
-                const char *proc;
-                size_t proc_len;
+        /* Shortcut for the simple case */
+        if (dir_fd == AT_FDCWD && strlen(path) < sizeof_field(struct sockaddr_un, sun_path))
+                return connect_unix_path_simple(fd, path);
 
-                /* If dir_fd is specified, then we need to go the indirect O_PATH route, because connectat()
-                 * does not exist. If the path is too long, we also need to take the indirect route, since we
-                 * can't fit this into a sockaddr_un directly. */
+        /* If dir_fd is specified, then we need to go the indirect O_PATH route, because connectat() does not
+         * exist. If the path is too long, we also need to take the indirect route, since we can't fit this
+         * into a sockaddr_un directly. */
 
-                inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC);
-                if (inode_fd < 0)
-                        return -errno;
+        inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC);
+        if (inode_fd < 0)
+                return -errno;
+
+        return connect_unix_inode(fd, inode_fd);
+}
+
+int socket_address_parse_unix(SocketAddress *ret_address, const char *s) {
+        struct sockaddr_un un;
+        int r;
+
+        assert(ret_address);
+        assert(s);
+
+        if (!IN_SET(*s, '/', '@'))
+                return -EPROTO;
+
+        r = sockaddr_un_set_path(&un, s);
+        if (r < 0)
+                return r;
+
+        *ret_address = (SocketAddress) {
+                .sockaddr.un = un,
+                .size = r,
+        };
 
-                proc = FORMAT_PROC_FD_PATH(inode_fd);
-                proc_len = strlen(proc);
+        return 0;
+}
+
+#if 0 /* NM_IGNORED */
+int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) {
+        /* AF_VSOCK socket in vsock:cid:port notation */
+        _cleanup_free_ char *n = NULL;
+        char *e, *cid_start;
+        unsigned port, cid;
+        int type, r;
+
+        assert(ret_address);
+        assert(s);
+
+        if ((cid_start = startswith(s, "vsock:")))
+                type = 0;
+        else if ((cid_start = startswith(s, "vsock-dgram:")))
+                type = SOCK_DGRAM;
+        else if ((cid_start = startswith(s, "vsock-seqpacket:")))
+                type = SOCK_SEQPACKET;
+        else if ((cid_start = startswith(s, "vsock-stream:")))
+                type = SOCK_STREAM;
+        else
+                return -EPROTO;
 
-                assert(proc_len < sizeof(sa.un.sun_path));
-                memcpy(sa.un.sun_path, proc, proc_len + 1);
-                salen = offsetof(struct sockaddr_un, sun_path) + proc_len + 1;
+        e = strchr(cid_start, ':');
+        if (!e)
+                return -EINVAL;
+
+        r = safe_atou(e+1, &port);
+        if (r < 0)
+                return r;
+
+        n = strndup(cid_start, e - cid_start);
+        if (!n)
+                return -ENOMEM;
+
+        if (isempty(n))
+                cid = VMADDR_CID_ANY;
+        else {
+                r = safe_atou(n, &cid);
+                if (r < 0)
+                        return r;
         }
 
-        return RET_NERRNO(connect(fd, &sa.sa, salen));
+        *ret_address = (SocketAddress) {
+                .sockaddr.vm = {
+                        .svm_cid = cid,
+                        .svm_family = AF_VSOCK,
+                        .svm_port = port,
+                },
+                .type = type,
+                .size = sizeof(struct sockaddr_vm),
+        };
+
+        return 0;
 }
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/socket-util.h b/src/libnm-systemd-shared/src/basic/socket-util.h
index cf9a0b35..26f9636f 100644
--- a/src/libnm-systemd-shared/src/basic/socket-util.h
+++ b/src/libnm-systemd-shared/src/basic/socket-util.h
@@ -177,15 +177,29 @@ int flush_accept(int fd);
 #define CMSG_FOREACH(cmsg, mh)                                          \
         for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg)))
 
+/* Returns the cmsghdr's data pointer, but safely cast to the specified type. Does two alignment checks: one
+ * at compile time, that the requested type has a smaller or same alignment as 'struct cmsghdr', and one
+ * during runtime, that the actual pointer matches the alignment too. This is supposed to catch cases such as
+ * 'struct timeval' is embedded into 'struct cmsghdr' on architectures where the alignment of the former is 8
+ * bytes (because of a 64bit time_t), but of the latter is 4 bytes (because size_t is 32bit), such as
+ * riscv32. */
+#define CMSG_TYPED_DATA(cmsg, type)                                     \
+        ({                                                              \
+                struct cmsghdr *_cmsg = (cmsg);                         \
+                assert_cc(alignof(type) <= alignof(struct cmsghdr));    \
+                _cmsg ? CAST_ALIGN_PTR(type, CMSG_DATA(_cmsg)) : (type*) NULL; \
+        })
+
 struct cmsghdr* cmsg_find(struct msghdr *mh, int level, int type, socklen_t length);
+void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, size_t buf_len);
 
 /* Type-safe, dereferencing version of cmsg_find() */
-#define CMSG_FIND_DATA(mh, level, type, ctype) \
-        ({                                                            \
-                struct cmsghdr *_found;                               \
-                _found = cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))); \
-                (ctype*) (_found ? CMSG_DATA(_found) : NULL);         \
-        })
+#define CMSG_FIND_DATA(mh, level, type, ctype)                          \
+        CMSG_TYPED_DATA(cmsg_find(mh, level, type, CMSG_LEN(sizeof(ctype))), ctype)
+
+/* Type-safe version of cmsg_find_and_copy_data() */
+#define CMSG_FIND_AND_COPY_DATA(mh, level, type, ctype)             \
+        (ctype*) cmsg_find_and_copy_data(mh, level, type, &(ctype){}, sizeof(ctype))
 
 /* Resolves to a type that can carry cmsghdr structures. Make sure things are properly aligned, i.e. the type
  * itself is placed properly in memory and the size is also aligned to what's appropriate for "cmsghdr"
@@ -306,7 +320,7 @@ struct timespec_large {
 
 ssize_t recvmsg_safe(int sockfd, struct msghdr *msg, int flags);
 
-int socket_get_family(int fd, int *ret);
+int socket_get_family(int fd);
 int socket_set_recvpktinfo(int fd, int af, bool b);
 int socket_set_unicast_if(int fd, int af, int ifi);
 
@@ -338,3 +352,16 @@ int socket_get_mtu(int fd, int af, size_t *ret);
 #define UCRED_INVALID { .pid = 0, .uid = UID_INVALID, .gid = GID_INVALID }
 
 int connect_unix_path(int fd, int dir_fd, const char *path);
+
+/* Parses AF_UNIX and AF_VSOCK addresses. AF_INET[6] require some netlink calls, so it cannot be in
+ * src/basic/ and is done from 'socket_local_address from src/shared/. Return -EPROTO in case of
+ * protocol mismatch. */
+int socket_address_parse_unix(SocketAddress *ret_address, const char *s);
+int socket_address_parse_vsock(SocketAddress *ret_address, const char *s);
+
+/* libc's SOMAXCONN is defined to 128 or 4096 (at least on glibc). But actually, the value can be much
+ * larger. In our codebase we want to set it to the max usually, since noawadays socket memory is properly
+ * tracked by memcg, and hence we don't need to enforce extra limits here. Moreover, the kernel caps it to
+ * /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file
+ * authoritative. */
+#define SOMAXCONN_DELUXE INT_MAX
diff --git a/src/libnm-systemd-shared/src/basic/stat-util.c b/src/libnm-systemd-shared/src/basic/stat-util.c
index 5a142263..a81ee468 100644
--- a/src/libnm-systemd-shared/src/basic/stat-util.c
+++ b/src/libnm-systemd-shared/src/basic/stat-util.c
@@ -10,13 +10,14 @@
 #include <unistd.h>
 
 #include "alloc-util.h"
-#include "chase-symlinks.h"
+#include "chase.h"
 #include "dirent-util.h"
 #include "errno-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "filesystems.h"
 #include "fs-util.h"
+#include "hash-funcs.h"
 #include "macro.h"
 #include "missing_fs.h"
 #include "missing_magic.h"
@@ -69,7 +70,7 @@ int is_device_node(const char *path) {
 }
 
 int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         struct dirent *buf;
         size_t m;
 
@@ -149,56 +150,58 @@ int null_or_empty_path_with_root(const char *fn, const char *root) {
         if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null"))
                 return true;
 
-        r = chase_symlinks_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st, NULL);
+        r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st);
         if (r < 0)
                 return r;
 
         return null_or_empty(&st);
 }
 
-int null_or_empty_fd(int fd) {
-        struct stat st;
+static int fd_is_read_only_fs(int fd) {
+        struct statvfs st;
 
         assert(fd >= 0);
 
-        if (fstat(fd, &st) < 0)
+        if (fstatvfs(fd, &st) < 0)
                 return -errno;
 
-        return null_or_empty(&st);
+        if (st.f_flag & ST_RDONLY)
+                return true;
+
+        /* On NFS, fstatvfs() might not reflect whether we can actually write to the remote share. Let's try
+         * again with access(W_OK) which is more reliable, at least sometimes. */
+        if (access_fd(fd, W_OK) == -EROFS)
+                return true;
+
+        return false;
 }
 
 int path_is_read_only_fs(const char *path) {
-        struct statvfs st;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(path);
 
-        if (statvfs(path, &st) < 0)
+        fd = open(path, O_CLOEXEC | O_PATH);
+        if (fd < 0)
                 return -errno;
 
-        if (st.f_flag & ST_RDONLY)
-                return true;
-
-        /* On NFS, statvfs() might not reflect whether we can actually
-         * write to the remote share. Let's try again with
-         * access(W_OK) which is more reliable, at least sometimes. */
-        if (access(path, W_OK) < 0 && errno == EROFS)
-                return true;
-
-        return false;
+        return fd_is_read_only_fs(fd);
 }
 #endif /* NM_IGNORED */
 
-int files_same(const char *filea, const char *fileb, int flags) {
+int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags) {
         struct stat a, b;
 
+        assert(fda >= 0 || fda == AT_FDCWD);
         assert(filea);
+        assert(fdb >= 0 || fdb == AT_FDCWD);
         assert(fileb);
 
-        if (fstatat(AT_FDCWD, filea, &a, flags) < 0)
-                return -errno;
+        if (fstatat(fda, filea, &a, flags) < 0)
+                return log_debug_errno(errno, "Cannot stat %s: %m", filea);
 
-        if (fstatat(AT_FDCWD, fileb, &b, flags) < 0)
-                return -errno;
+        if (fstatat(fdb, fileb, &b, flags) < 0)
+                return log_debug_errno(errno, "Cannot stat %s: %m", fileb);
 
         return stat_inode_same(&a, &b);
 }
@@ -210,22 +213,13 @@ bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) {
         return F_TYPE_EQUAL(s->f_type, magic_value);
 }
 
-#if 0 /* NM_IGNORED */
-int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
-        struct statfs s;
-
-        if (fstatfs(fd, &s) < 0)
-                return -errno;
-
-        return is_fs_type(&s, magic_value);
-}
-#endif /* NM_IGNORED */
-
-int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
+int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value) {
         struct statfs s;
+        int r;
 
-        if (statfs(path, &s) < 0)
-                return -errno;
+        r = xstatfsat(dir_fd, path, &s);
+        if (r < 0)
+                return r;
 
         return is_fs_type(&s, magic_value);
 }
@@ -306,6 +300,18 @@ int fd_verify_regular(int fd) {
 }
 
 #if 0 /* NM_IGNORED */
+int verify_regular_at(int dir_fd, const char *path, bool follow) {
+        struct stat st;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
+        if (fstatat(dir_fd, path, &st, (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0)
+                return -errno;
+
+        return stat_verify_regular(&st);
+}
+
 int stat_verify_directory(const struct stat *st) {
         assert(st);
 
@@ -455,3 +461,58 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
         return 0;
 }
 #endif /* NM_IGNORED */
+
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret) {
+        _cleanup_close_ int fd = -EBADF;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(ret);
+
+        fd = xopenat(dir_fd, path, O_PATH|O_CLOEXEC|O_NOCTTY, /* xopen_flags = */ 0, /* mode = */ 0);
+        if (fd < 0)
+                return fd;
+
+        return RET_NERRNO(fstatfs(fd, ret));
+}
+
+#if 0 /* NM_IGNORED */
+void inode_hash_func(const struct stat *q, struct siphash *state) {
+        siphash24_compress(&q->st_dev, sizeof(q->st_dev), state);
+        siphash24_compress(&q->st_ino, sizeof(q->st_ino), state);
+}
+
+int inode_compare_func(const struct stat *a, const struct stat *b) {
+        int r;
+
+        r = CMP(a->st_dev, b->st_dev);
+        if (r != 0)
+                return r;
+
+        return CMP(a->st_ino, b->st_ino);
+}
+
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(inode_hash_ops, struct stat, inode_hash_func, inode_compare_func, free);
+
+const char* inode_type_to_string(mode_t m) {
+
+        /* Returns a short string for the inode type. We use the same name as the underlying macros for each
+         * inode type. */
+
+        switch (m & S_IFMT) {
+        case S_IFREG:
+                return "reg";
+        case S_IFDIR:
+                return "dir";
+        case S_IFCHR:
+                return "chr";
+        case S_IFBLK:
+                return "blk";
+        case S_IFIFO:
+                return "fifo";
+        case S_IFSOCK:
+                return "sock";
+        }
+
+        return NULL;
+}
+#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/stat-util.h b/src/libnm-systemd-shared/src/basic/stat-util.h
index f9519d8c..ae0aaf8f 100644
--- a/src/libnm-systemd-shared/src/basic/stat-util.h
+++ b/src/libnm-systemd-shared/src/basic/stat-util.h
@@ -11,6 +11,7 @@
 
 #include "macro.h"
 #include "missing_stat.h"
+#include "siphash24.h"
 
 int is_symlink(const char *path);
 int is_dir_full(int atfd, const char *fname, bool follow);
@@ -29,7 +30,6 @@ static inline int dir_is_empty(const char *path, bool ignore_hidden_or_backup) {
 
 bool null_or_empty(struct stat *st) _pure_;
 int null_or_empty_path_with_root(const char *fn, const char *root);
-int null_or_empty_fd(int fd);
 
 static inline int null_or_empty_path(const char *fn) {
         return null_or_empty_path_with_root(fn, NULL);
@@ -37,15 +37,24 @@ static inline int null_or_empty_path(const char *fn) {
 
 int path_is_read_only_fs(const char *path);
 
-int files_same(const char *filea, const char *fileb, int flags);
+int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags);
+
+static inline int inode_same(const char *filea, const char *fileb, int flags) {
+        return inode_same_at(AT_FDCWD, filea, AT_FDCWD, fileb, flags);
+}
 
 /* The .f_type field of struct statfs is really weird defined on
  * different archs. Let's give its type a name. */
 typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t;
 
 bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_;
-int fd_is_fs_type(int fd, statfs_f_type_t magic_value);
-int path_is_fs_type(const char *path, statfs_f_type_t magic_value);
+int is_fs_type_at(int dir_fd, const char *path, statfs_f_type_t magic_value);
+static inline int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
+        return is_fs_type_at(fd, NULL, magic_value);
+}
+static inline int path_is_fs_type(const char *path, statfs_f_type_t magic_value) {
+        return is_fs_type_at(AT_FDCWD, path, magic_value);
+}
 
 bool is_temporary_fs(const struct statfs *s) _pure_;
 bool is_network_fs(const struct statfs *s) _pure_;
@@ -64,6 +73,7 @@ int path_is_network_fs(const char *path);
 
 int stat_verify_regular(const struct stat *st);
 int fd_verify_regular(int fd);
+int verify_regular_at(int dir_fd, const char *path, bool follow);
 
 int stat_verify_directory(const struct stat *st);
 int fd_verify_directory(int fd);
@@ -78,6 +88,8 @@ bool statx_mount_same(const struct new_statx *a, const struct new_statx *b);
 
 int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct statx *sx);
 
+int xstatfsat(int dir_fd, const char *path, struct statfs *ret);
+
 #if HAS_FEATURE_MEMORY_SANITIZER
 #  warning "Explicitly initializing struct statx, to work around msan limitation. Please remove as soon as msan has been updated to not require this."
 #  define STRUCT_STATX_DEFINE(var)              \
@@ -96,3 +108,9 @@ int statx_fallback(int dfd, const char *path, int flags, unsigned mask, struct s
                 struct new_statx nsx;           \
         } var
 #endif
+
+void inode_hash_func(const struct stat *q, struct siphash *state);
+int inode_compare_func(const struct stat *a, const struct stat *b);
+extern const struct hash_ops inode_hash_ops;
+
+const char* inode_type_to_string(mode_t m);
diff --git a/src/libnm-systemd-shared/src/basic/stdio-util.h b/src/libnm-systemd-shared/src/basic/stdio-util.h
index 07cbfe9d..d0b25fdf 100644
--- a/src/libnm-systemd-shared/src/basic/stdio-util.h
+++ b/src/libnm-systemd-shared/src/basic/stdio-util.h
@@ -9,15 +9,20 @@
 #include <sys/types.h>
 
 #include "macro.h"
-#include "memory-util.h"
 
-#define snprintf_ok(buf, len, fmt, ...)                                 \
-        ({                                                              \
-                char *_buf = (buf);                                     \
-                size_t _len = (len);                                    \
-                int _snpf = snprintf(_buf, _len, (fmt), ##__VA_ARGS__); \
-                _snpf >= 0 && (size_t) _snpf < _len ? _buf : NULL;      \
-        })
+_printf_(3, 4)
+static inline char *snprintf_ok(char *buf, size_t len, const char *format, ...) {
+        va_list ap;
+        int r;
+
+        va_start(ap, format);
+        DISABLE_WARNING_FORMAT_NONLITERAL;
+        r = vsnprintf(buf, len, format, ap);
+        REENABLE_WARNING;
+        va_end(ap);
+
+        return r >= 0 && (size_t) r < len ? buf : NULL;
+}
 
 #define xsprintf(buf, fmt, ...) \
         assert_message_se(snprintf_ok(buf, ELEMENTSOF(buf), fmt, ##__VA_ARGS__), "xsprintf: " #buf "[] must be big enough")
@@ -28,7 +33,7 @@ do {                                                                    \
         size_t _i, _k;                                                  \
         /* See https://github.com/google/sanitizers/issues/992 */       \
         if (HAS_FEATURE_MEMORY_SANITIZER)                               \
-                zero(_argtypes);                                        \
+                memset(_argtypes, 0, sizeof(_argtypes));                \
         _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \
         assert(_k < ELEMENTSOF(_argtypes));                             \
         for (_i = 0; _i < _k; _i++) {                                   \
diff --git a/src/libnm-systemd-shared/src/basic/string-table.h b/src/libnm-systemd-shared/src/basic/string-table.h
index e3a26a62..3be70dfa 100644
--- a/src/libnm-systemd-shared/src/basic/string-table.h
+++ b/src/libnm-systemd-shared/src/basic/string-table.h
@@ -95,6 +95,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k
 #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max)         \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,)  \
         _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
+#define DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(name,type,max) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,)
 
 #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \
         _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static)
diff --git a/src/libnm-systemd-shared/src/basic/string-util.c b/src/libnm-systemd-shared/src/basic/string-util.c
index 9b2ee879..1afa49bb 100644
--- a/src/libnm-systemd-shared/src/basic/string-util.c
+++ b/src/libnm-systemd-shared/src/basic/string-util.c
@@ -11,16 +11,17 @@
 #include "alloc-util.h"
 #include "escape.h"
 #include "extract-word.h"
+#include "fd-util.h"
 #include "fileio.h"
 #include "gunicode.h"
 #include "locale-util.h"
 #include "macro.h"
 #include "memory-util.h"
+#include "memstream-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "terminal-util.h"
 #include "utf8.h"
-#include "util.h"
 
 char* first_word(const char *s, const char *word) {
         size_t sl, wl;
@@ -611,8 +612,8 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
                 STATE_CSI,
                 STATE_CSO,
         } state = STATE_OTHER;
-        char *obuf = NULL;
-        size_t osz = 0, isz, shift[2] = {}, n_carriage_returns = 0;
+        _cleanup_(memstream_done) MemStream m = {};
+        size_t isz, shift[2] = {}, n_carriage_returns = 0;
         FILE *f;
 
         assert(ibuf);
@@ -636,7 +637,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
 
         /* Note we turn off internal locking on f for performance reasons. It's safe to do so since we
          * created f here and it doesn't leave our scope. */
-        f = open_memstream_unlocked(&obuf, &osz);
+        f = memstream_init(&m);
         if (!f)
                 return NULL;
 
@@ -721,17 +722,12 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
                 }
         }
 
-        if (fflush_and_check(f) < 0) {
-                fclose(f);
-                return mfree(obuf);
-        }
-        fclose(f);
+        char *obuf;
+        if (memstream_finalize(&m, &obuf, _isz) < 0)
+                return NULL;
 
         free_and_replace(*ibuf, obuf);
 
-        if (_isz)
-                *_isz = osz;
-
         if (highlight) {
                 highlight[0] += shift[0];
                 highlight[1] += shift[1];
@@ -739,6 +735,7 @@ char *strip_tab_ansi(char **ibuf, size_t *_isz, size_t highlight[2]) {
 
         return *ibuf;
 }
+#endif /* NM_IGNORED */
 
 char *strextend_with_separator_internal(char **x, const char *separator, ...) {
         size_t f, l, l_separator;
@@ -809,7 +806,6 @@ char *strextend_with_separator_internal(char **x, const char *separator, ...) {
 
         return p;
 }
-#endif /* NM_IGNORED */
 
 int strextendf_with_separator(char **x, const char *separator, const char *format, ...) {
         size_t m, a, l_separator;
@@ -964,8 +960,7 @@ int free_and_strdup(char **p, const char *s) {
         } else
                 t = NULL;
 
-        free(*p);
-        *p = t;
+        free_and_replace(*p, t);
 
         return 1;
 }
@@ -1197,6 +1192,49 @@ char *string_replace_char(char *str, char old_char, char new_char) {
         return str;
 }
 
+int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret) {
+        char *b;
+
+        assert(s || n == 0);
+        assert(mode >= 0);
+        assert(mode < _MAKE_CSTRING_MODE_MAX);
+
+        /* Converts a sized character buffer into a NUL-terminated NUL string, refusing if there are embedded
+         * NUL bytes. Whether to expect a trailing NUL byte can be specified via 'mode' */
+
+        if (n == 0) {
+                if (mode == MAKE_CSTRING_REQUIRE_TRAILING_NUL)
+                        return -EINVAL;
+
+                if (!ret)
+                        return 0;
+
+                b = new0(char, 1);
+        } else {
+                const char *nul;
+
+                nul = memchr(s, 0, n);
+                if (nul) {
+                        if (nul < s + n - 1 || /* embedded NUL? */
+                            mode == MAKE_CSTRING_REFUSE_TRAILING_NUL)
+                                return -EINVAL;
+
+                        n--;
+                } else if (mode == MAKE_CSTRING_REQUIRE_TRAILING_NUL)
+                        return -EINVAL;
+
+                if (!ret)
+                        return 0;
+
+                b = memdup_suffix0(s, n);
+        }
+        if (!b)
+                return -ENOMEM;
+
+        *ret = b;
+        return 0;
+}
+
 size_t strspn_from_end(const char *str, const char *accept) {
         size_t n = 0;
 
@@ -1211,4 +1249,55 @@ size_t strspn_from_end(const char *str, const char *accept) {
 
         return n;
 }
+
+char *strdupspn(const char *a, const char *accept) {
+        if (isempty(a) || isempty(accept))
+                return strdup("");
+
+        return strndup(a, strspn(a, accept));
+}
+
+char *strdupcspn(const char *a, const char *reject) {
+        if (isempty(a))
+                return strdup("");
+        if (isempty(reject))
+                return strdup(a);
+
+        return strndup(a, strcspn(a, reject));
+}
+
+char *find_line_startswith(const char *haystack, const char *needle) {
+        char *p;
+
+        assert(haystack);
+        assert(needle);
+
+        /* Finds the first line in 'haystack' that starts with the specified string. Returns a pointer to the
+         * first character after it */
+
+        p = strstr(haystack, needle);
+        if (!p)
+                return NULL;
+
+        if (p > haystack)
+                while (p[-1] != '\n') {
+                        p = strstr(p + 1, needle);
+                        if (!p)
+                                return NULL;
+                }
+
+        return p + strlen(needle);
+}
 #endif /* NM_IGNORED */
+
+char *startswith_strv(const char *string, char **strv) {
+        char *found = NULL;
+
+        STRV_FOREACH(i, strv) {
+                found = startswith(string, *i);
+                if (found)
+                        break;
+        }
+
+        return found;
+}
diff --git a/src/libnm-systemd-shared/src/basic/string-util.h b/src/libnm-systemd-shared/src/basic/string-util.h
index 46681ced..4430910e 100644
--- a/src/libnm-systemd-shared/src/basic/string-util.h
+++ b/src/libnm-systemd-shared/src/basic/string-util.h
@@ -29,6 +29,18 @@ static inline char* strstr_ptr(const char *haystack, const char *needle) {
         return strstr(haystack, needle);
 }
 
+static inline char *strstrafter(const char *haystack, const char *needle) {
+        char *p;
+
+        /* Returns NULL if not found, or pointer to first character after needle if found */
+
+        p = strstr_ptr(haystack, needle);
+        if (!p)
+                return NULL;
+
+        return p + strlen(needle);
+}
+
 static inline const char* strnull(const char *s) {
         return s ?: "(null)";
 }
@@ -53,9 +65,13 @@ static inline const char* enable_disable(bool b) {
         return b ? "enable" : "disable";
 }
 
-static inline const char *empty_to_null(const char *p) {
-        return isempty(p) ? NULL : p;
-}
+/* This macro's return pointer will have the "const" qualifier set or unset the same way as the input
+ * pointer. */
+#define empty_to_null(p)                                \
+        ({                                              \
+                const char *_p = (p);                   \
+                (typeof(p)) (isempty(_p) ? NULL : _p);  \
+        })
 
 static inline const char *empty_to_na(const char *p) {
         return isempty(p) ? "n/a" : p;
@@ -74,6 +90,11 @@ static inline bool empty_or_dash(const char *str) {
 static inline const char *empty_or_dash_to_null(const char *p) {
         return empty_or_dash(p) ? NULL : p;
 }
+#define empty_or_dash_to_null(p)                                \
+        ({                                                      \
+                const char *_p = (p);                           \
+                (typeof(p)) (empty_or_dash(_p) ? NULL : _p);    \
+        })
 
 char *first_word(const char *s, const char *word) _pure_;
 
@@ -230,4 +251,24 @@ bool streq_skip_trailing_chars(const char *s1, const char *s2, const char *ok);
 
 char *string_replace_char(char *str, char old_char, char new_char);
 
+typedef enum MakeCStringMode {
+        MAKE_CSTRING_REFUSE_TRAILING_NUL,
+        MAKE_CSTRING_ALLOW_TRAILING_NUL,
+        MAKE_CSTRING_REQUIRE_TRAILING_NUL,
+        _MAKE_CSTRING_MODE_MAX,
+        _MAKE_CSTRING_MODE_INVALID = -1,
+} MakeCStringMode;
+
+int make_cstring(const char *s, size_t n, MakeCStringMode mode, char **ret);
+
 size_t strspn_from_end(const char *str, const char *accept);
+
+char *strdupspn(const char *a, const char *accept);
+char *strdupcspn(const char *a, const char *reject);
+
+char *find_line_startswith(const char *haystack, const char *needle);
+
+char *startswith_strv(const char *string, char **strv);
+
+#define STARTSWITH_SET(p, ...)                                  \
+        startswith_strv(p, STRV_MAKE(__VA_ARGS__))
diff --git a/src/libnm-systemd-shared/src/basic/strv.c b/src/libnm-systemd-shared/src/basic/strv.c
index 48ca8575..9ad53307 100644
--- a/src/libnm-systemd-shared/src/basic/strv.c
+++ b/src/libnm-systemd-shared/src/basic/strv.c
@@ -9,6 +9,7 @@
 #include <stdlib.h>
 
 #include "alloc-util.h"
+#include "env-util.h"
 #include "escape.h"
 #include "extract-word.h"
 #include "fileio.h"
@@ -65,6 +66,16 @@ char* strv_find_startswith(char * const *l, const char *name) {
         return NULL;
 }
 
+char* strv_find_first_field(char * const *needles, char * const *haystack) {
+        STRV_FOREACH(k, needles) {
+                char *value = strv_env_pairs_get((char **)haystack, *k);
+                if (value)
+                        return value;
+        }
+
+        return NULL;
+}
+
 char** strv_free(char **l) {
         STRV_FOREACH(k, l)
                 free(*k);
@@ -79,20 +90,26 @@ char** strv_free_erase(char **l) {
         return mfree(l);
 }
 
-char** strv_copy(char * const *l) {
+char** strv_copy_n(char * const *l, size_t m) {
         _cleanup_strv_free_ char **result = NULL;
         char **k;
 
-        result = new(char*, strv_length(l) + 1);
+        result = new(char*, MIN(strv_length(l), m) + 1);
         if (!result)
                 return NULL;
 
         k = result;
         STRV_FOREACH(i, l) {
+                if (m == 0)
+                        break;
+
                 *k = strdup(*i);
                 if (!*k)
                         return NULL;
                 k++;
+
+                if (m != SIZE_MAX)
+                        m--;
         }
 
         *k = NULL;
@@ -629,123 +646,6 @@ char** strv_remove(char **l, const char *s) {
         return l;
 }
 
-char** strv_parse_nulstr(const char *s, size_t l) {
-        /* l is the length of the input data, which will be split at NULs into
-         * elements of the resulting strv. Hence, the number of items in the resulting strv
-         * will be equal to one plus the number of NUL bytes in the l bytes starting at s,
-         * unless s[l-1] is NUL, in which case the final empty string is not stored in
-         * the resulting strv, and length is equal to the number of NUL bytes.
-         *
-         * Note that contrary to a normal nulstr which cannot contain empty strings, because
-         * the input data is terminated by any two consequent NUL bytes, this parser accepts
-         * empty strings in s.
-         */
-
-        size_t c = 0, i = 0;
-        char **v;
-
-        assert(s || l <= 0);
-
-        if (l <= 0)
-                return new0(char*, 1);
-
-        for (const char *p = s; p < s + l; p++)
-                if (*p == 0)
-                        c++;
-
-        if (s[l-1] != 0)
-                c++;
-
-        v = new0(char*, c+1);
-        if (!v)
-                return NULL;
-
-        for (const char *p = s; p < s + l; ) {
-                const char *e;
-
-                e = memchr(p, 0, s + l - p);
-
-                v[i] = strndup(p, e ? e - p : s + l - p);
-                if (!v[i]) {
-                        strv_free(v);
-                        return NULL;
-                }
-
-                i++;
-
-                if (!e)
-                        break;
-
-                p = e + 1;
-        }
-
-        assert(i == c);
-
-        return v;
-}
-
-#if 0 /* NM_IGNORED */
-char** strv_split_nulstr(const char *s) {
-        const char *i;
-        char **r = NULL;
-
-        NULSTR_FOREACH(i, s)
-                if (strv_extend(&r, i) < 0) {
-                        strv_free(r);
-                        return NULL;
-                }
-
-        if (!r)
-                return strv_new(NULL);
-
-        return r;
-}
-#endif /* NM_IGNORED */
-
-int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) {
-        /* A valid nulstr with two NULs at the end will be created, but
-         * q will be the length without the two trailing NULs. Thus the output
-         * string is a valid nulstr and can be iterated over using NULSTR_FOREACH,
-         * and can also be parsed by strv_parse_nulstr as long as the length
-         * is provided separately.
-         */
-
-        _cleanup_free_ char *m = NULL;
-        size_t n = 0;
-
-        assert(ret);
-        assert(ret_size);
-
-        STRV_FOREACH(i, l) {
-                size_t z;
-
-                z = strlen(*i);
-
-                if (!GREEDY_REALLOC(m, n + z + 2))
-                        return -ENOMEM;
-
-                memcpy(m + n, *i, z + 1);
-                n += z + 1;
-        }
-
-        if (!m) {
-                m = new0(char, 1);
-                if (!m)
-                        return -ENOMEM;
-                n = 1;
-        } else
-                /* make sure there is a second extra NUL at the end of resulting nulstr */
-                m[n] = '\0';
-
-        assert(n > 0);
-        *ret = m;
-        *ret_size = n - 1;
-
-        m = NULL;
-
-        return 0;
-}
-
 bool strv_overlap(char * const *a, char * const *b) {
         STRV_FOREACH(i, a)
                 if (strv_contains(b, *i))
@@ -787,9 +687,9 @@ int strv_compare(char * const *a, char * const *b) {
         return 0;
 }
 
-void strv_print(char * const *l) {
+void strv_print_full(char * const *l, const char *prefix) {
         STRV_FOREACH(s, l)
-                puts(*s);
+                printf("%s%s\n", strempty(prefix), *s);
 }
 
 int strv_extendf(char ***l, const char *format, ...) {
@@ -832,8 +732,7 @@ char** strv_shell_escape(char **l, const char *bad) {
                 if (!v)
                         return NULL;
 
-                free(*s);
-                *s = v;
+                free_and_replace(*s, v);
         }
 
         return l;
@@ -916,6 +815,22 @@ rollback:
         return -ENOMEM;
 }
 
+int strv_extend_assignment(char ***l, const char *lhs, const char *rhs) {
+        char *j;
+
+        assert(l);
+        assert(lhs);
+
+        if (!rhs) /* value is optional, in which case we suppress the field */
+                return 0;
+
+        j = strjoin(lhs, "=", rhs);
+        if (!j)
+                return -ENOMEM;
+
+        return strv_consume(l, j);
+}
+
 int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) {
         bool b = false;
         int r;
diff --git a/src/libnm-systemd-shared/src/basic/strv.h b/src/libnm-systemd-shared/src/basic/strv.h
index d6f5ac6b..544d46a3 100644
--- a/src/libnm-systemd-shared/src/basic/strv.h
+++ b/src/libnm-systemd-shared/src/basic/strv.h
@@ -17,6 +17,9 @@ char* strv_find(char * const *l, const char *name) _pure_;
 char* strv_find_case(char * const *l, const char *name) _pure_;
 char* strv_find_prefix(char * const *l, const char *name) _pure_;
 char* strv_find_startswith(char * const *l, const char *name) _pure_;
+/* Given two vectors, the first a list of keys and the second a list of key-value pairs, returns the value
+ * of the first key from the first vector that is found in the second vector. */
+char* strv_find_first_field(char * const *needles, char * const *haystack) _pure_;
 
 #define strv_contains(l, s) (!!strv_find((l), (s)))
 #define strv_contains_case(l, s) (!!strv_find_case((l), (s)))
@@ -29,7 +32,10 @@ char** strv_free_erase(char **l);
 DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase);
 #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep)
 
-char** strv_copy(char * const *l);
+char** strv_copy_n(char * const *l, size_t n);
+static inline char** strv_copy(char * const *l) {
+        return strv_copy_n(l, SIZE_MAX);
+}
 size_t strv_length(char * const *l) _pure_;
 
 int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates);
@@ -45,7 +51,7 @@ static inline int strv_extend(char ***l, const char *value) {
         return strv_extend_with_size(l, NULL, value);
 }
 
-int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
+int strv_extendf(char ***l, const char *format, ...) _printf_(2,3);
 int strv_extend_front(char ***l, const char *value);
 
 int strv_push_with_size(char ***l, size_t *n, char *value);
@@ -84,7 +90,7 @@ char** strv_new_ap(const char *x, va_list ap);
 #define STRV_IGNORE ((const char *) POINTER_MAX)
 
 static inline const char* STRV_IFNOTNULL(const char *x) {
-        return x ? x : STRV_IGNORE;
+        return x ?: STRV_IGNORE;
 }
 
 static inline bool strv_isempty(char * const *l) {
@@ -124,20 +130,6 @@ static inline char *strv_join(char * const *l, const char *separator) {
         return strv_join_full(l, separator, NULL, false);
 }
 
-char** strv_parse_nulstr(const char *s, size_t l);
-char** strv_split_nulstr(const char *s);
-int strv_make_nulstr(char * const *l, char **p, size_t *n);
-
-static inline int strv_from_nulstr(char ***a, const char *nulstr) {
-        char **t;
-
-        t = strv_split_nulstr(nulstr);
-        if (!t)
-                return -ENOMEM;
-        *a = t;
-        return 0;
-}
-
 bool strv_overlap(char * const *a, char * const *b) _pure_;
 
 #define _STRV_FOREACH_BACKWARDS(s, l, h, i)                             \
@@ -160,7 +152,10 @@ bool strv_overlap(char * const *a, char * const *b) _pure_;
         _STRV_FOREACH_PAIR(x, y, l, UNIQ_T(i, UNIQ))
 
 char** strv_sort(char **l);
-void strv_print(char * const *l);
+void strv_print_full(char * const *l, const char *prefix);
+static inline void strv_print(char * const *l) {
+        strv_print_full(l, NULL);
+}
 
 #define strv_from_stdarg_alloca(first)                          \
         ({                                                      \
@@ -205,18 +200,6 @@ void strv_print(char * const *l);
                 _x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \
         })
 
-#define STARTSWITH_SET(p, ...)                                  \
-        ({                                                      \
-                const char *_p = (p);                           \
-                char *_found = NULL;                            \
-                STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) {      \
-                        _found = startswith(_p, *_i);           \
-                        if (_found)                             \
-                                break;                          \
-                }                                               \
-                _found;                                         \
-        })
-
 #define ENDSWITH_SET(p, ...)                                    \
         ({                                                      \
                 const char *_p = (p);                           \
@@ -255,6 +238,8 @@ char** strv_skip(char **l, size_t n);
 
 int strv_extend_n(char ***l, const char *value, size_t n);
 
+int strv_extend_assignment(char ***l, const char *lhs, const char *rhs);
+
 int fputstrv(FILE *f, char * const *l, const char *separator, bool *space);
 
 #define strv_free_and_replace(a, b)             \
diff --git a/src/libnm-systemd-shared/src/basic/time-util.c b/src/libnm-systemd-shared/src/basic/time-util.c
index cf556a73..092912b2 100644
--- a/src/libnm-systemd-shared/src/basic/time-util.c
+++ b/src/libnm-systemd-shared/src/basic/time-util.c
@@ -19,6 +19,7 @@
 #include "io-util.h"
 #include "log.h"
 #include "macro.h"
+#include "missing_threads.h"
 #include "missing_timerfd.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -173,6 +174,8 @@ dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) {
 dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
         usec_t nowm;
 
+        assert(ts);
+
         if (u == USEC_INFINITY) {
                 ts->realtime = ts->monotonic = USEC_INFINITY;
                 return ts;
@@ -185,6 +188,7 @@ dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) {
 }
 
 usec_t triple_timestamp_by_clock(triple_timestamp *ts, clockid_t clock) {
+        assert(ts);
 
         switch (clock) {
 
@@ -230,7 +234,7 @@ nsec_t timespec_load_nsec(const struct timespec *ts) {
         return (nsec_t) ts->tv_sec * NSEC_PER_SEC + (nsec_t) ts->tv_nsec;
 }
 
-struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
+struct timespec *timespec_store(struct timespec *ts, usec_t u) {
         assert(ts);
 
         if (u == USEC_INFINITY ||
@@ -246,7 +250,7 @@ struct timespec *timespec_store(struct timespec *ts, usec_t u)  {
         return ts;
 }
 
-struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n)  {
+struct timespec *timespec_store_nsec(struct timespec *ts, nsec_t n) {
         assert(ts);
 
         if (n == NSEC_INFINITY ||
@@ -311,54 +315,48 @@ char *format_timestamp_style(
         };
 
         struct tm tm;
+        bool utc, us;
         time_t sec;
         size_t n;
-        bool utc = false, us = false;
-        int r;
 
         assert(buf);
+        assert(style >= 0);
+        assert(style < _TIMESTAMP_STYLE_MAX);
 
-        switch (style) {
-                case TIMESTAMP_PRETTY:
-                case TIMESTAMP_UNIX:
-                        break;
-                case TIMESTAMP_US:
-                        us = true;
-                        break;
-                case TIMESTAMP_UTC:
-                        utc = true;
-                        break;
-                case TIMESTAMP_US_UTC:
-                        us = true;
-                        utc = true;
-                        break;
-                default:
-                        return NULL;
-        }
-
-        if (l < (size_t) (3 +                  /* week day */
-                          1 + 10 +             /* space and date */
-                          1 + 8 +              /* space and time */
-                          (us ? 1 + 6 : 0) +   /* "." and microsecond part */
-                          1 + 1 +              /* space and shortest possible zone */
-                          1))
-                return NULL; /* Not enough space even for the shortest form. */
         if (!timestamp_is_set(t))
                 return NULL; /* Timestamp is unset */
 
         if (style == TIMESTAMP_UNIX) {
-                r = snprintf(buf, l, "@" USEC_FMT, t / USEC_PER_SEC);  /* round down µs → s */
-                if (r < 0 || (size_t) r >= l)
-                        return NULL; /* Doesn't fit */
+                if (l < (size_t) (1 + 1 + 1))
+                        return NULL; /* not enough space for even the shortest of forms */
 
-                return buf;
+                return snprintf_ok(buf, l, "@" USEC_FMT, t / USEC_PER_SEC);  /* round down µs → s */
         }
 
+        utc = IN_SET(style, TIMESTAMP_UTC, TIMESTAMP_US_UTC, TIMESTAMP_DATE);
+        us = IN_SET(style, TIMESTAMP_US, TIMESTAMP_US_UTC);
+
+        if (l < (size_t) (3 +                   /* week day */
+                          1 + 10 +              /* space and date */
+                          style == TIMESTAMP_DATE ? 0 :
+                          (1 + 8 +              /* space and time */
+                           (us ? 1 + 6 : 0) +   /* "." and microsecond part */
+                           1 + (utc ? 3 : 1)) + /* space and shortest possible zone */
+                          1))
+                return NULL; /* Not enough space even for the shortest form. */
+
         /* Let's not format times with years > 9999 */
         if (t > USEC_TIMESTAMP_FORMATTABLE_MAX) {
-                assert(l >= STRLEN("--- XXXX-XX-XX XX:XX:XX") + 1);
-                strcpy(buf, "--- XXXX-XX-XX XX:XX:XX");
-                return buf;
+                static const char* const xxx[_TIMESTAMP_STYLE_MAX] = {
+                        [TIMESTAMP_PRETTY] = "--- XXXX-XX-XX XX:XX:XX",
+                        [TIMESTAMP_US]     = "--- XXXX-XX-XX XX:XX:XX.XXXXXX",
+                        [TIMESTAMP_UTC]    = "--- XXXX-XX-XX XX:XX:XX UTC",
+                        [TIMESTAMP_US_UTC] = "--- XXXX-XX-XX XX:XX:XX.XXXXXX UTC",
+                        [TIMESTAMP_DATE]   = "--- XXXX-XX-XX",
+                };
+
+                assert(l >= strlen(xxx[style]) + 1);
+                return strcpy(buf, xxx[style]);
         }
 
         sec = (time_t) (t / USEC_PER_SEC); /* Round down */
@@ -370,6 +368,14 @@ char *format_timestamp_style(
         assert((size_t) tm.tm_wday < ELEMENTSOF(weekdays));
         memcpy(buf, weekdays[tm.tm_wday], 4);
 
+        if (style == TIMESTAMP_DATE) {
+                /* Special format string if only date should be shown. */
+                if (strftime(buf + 3, l - 3, " %Y-%m-%d", &tm) <= 0)
+                        return NULL; /* Doesn't fit */
+
+                return buf;
+        }
+
         /* Add the main components */
         if (strftime(buf + 3, l - 3, " %Y-%m-%d %H:%M:%S", &tm) <= 0)
                 return NULL; /* Doesn't fit */
@@ -419,27 +425,29 @@ char *format_timestamp_style(
         return buf;
 }
 
-char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
+char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) {
         const char *s;
         usec_t n, d;
 
+        assert(buf);
+
         if (!timestamp_is_set(t))
                 return NULL;
 
-        n = now(CLOCK_REALTIME);
+        n = now(clock);
         if (n > t) {
                 d = n - t;
-                s = "ago";
+                s = " ago";
         } else {
                 d = t - n;
-                s = "left";
+                s = implicit_left ? "" : " left";
         }
 
         if (d >= USEC_PER_YEAR) {
                 usec_t years = d / USEC_PER_YEAR;
                 usec_t months = (d % USEC_PER_YEAR) / USEC_PER_MONTH;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 years,
                                 years == 1 ? "year" : "years",
                                 months,
@@ -449,7 +457,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
                 usec_t months = d / USEC_PER_MONTH;
                 usec_t days = (d % USEC_PER_MONTH) / USEC_PER_DAY;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 months,
                                 months == 1 ? "month" : "months",
                                 days,
@@ -459,39 +467,39 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
                 usec_t weeks = d / USEC_PER_WEEK;
                 usec_t days = (d % USEC_PER_WEEK) / USEC_PER_DAY;
 
-                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s %s",
+                (void) snprintf(buf, l, USEC_FMT " %s " USEC_FMT " %s%s",
                                 weeks,
                                 weeks == 1 ? "week" : "weeks",
                                 days,
                                 days == 1 ? "day" : "days",
                                 s);
         } else if (d >= 2*USEC_PER_DAY)
-                (void) snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s);
+                (void) snprintf(buf, l, USEC_FMT " days%s", d / USEC_PER_DAY,s);
         else if (d >= 25*USEC_PER_HOUR)
-                (void) snprintf(buf, l, "1 day " USEC_FMT "h %s",
+                (void) snprintf(buf, l, "1 day " USEC_FMT "h%s",
                                 (d - USEC_PER_DAY) / USEC_PER_HOUR, s);
         else if (d >= 6*USEC_PER_HOUR)
-                (void) snprintf(buf, l, USEC_FMT "h %s",
+                (void) snprintf(buf, l, USEC_FMT "h%s",
                                 d / USEC_PER_HOUR, s);
         else if (d >= USEC_PER_HOUR)
-                (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s",
+                (void) snprintf(buf, l, USEC_FMT "h " USEC_FMT "min%s",
                                 d / USEC_PER_HOUR,
                                 (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s);
         else if (d >= 5*USEC_PER_MINUTE)
-                (void) snprintf(buf, l, USEC_FMT "min %s",
+                (void) snprintf(buf, l, USEC_FMT "min%s",
                                 d / USEC_PER_MINUTE, s);
         else if (d >= USEC_PER_MINUTE)
-                (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s",
+                (void) snprintf(buf, l, USEC_FMT "min " USEC_FMT "s%s",
                                 d / USEC_PER_MINUTE,
                                 (d % USEC_PER_MINUTE) / USEC_PER_SEC, s);
         else if (d >= USEC_PER_SEC)
-                (void) snprintf(buf, l, USEC_FMT "s %s",
+                (void) snprintf(buf, l, USEC_FMT "s%s",
                                 d / USEC_PER_SEC, s);
         else if (d >= USEC_PER_MSEC)
-                (void) snprintf(buf, l, USEC_FMT "ms %s",
+                (void) snprintf(buf, l, USEC_FMT "ms%s",
                                 d / USEC_PER_MSEC, s);
         else if (d > 0)
-                (void) snprintf(buf, l, USEC_FMT"us %s",
+                (void) snprintf(buf, l, USEC_FMT"us%s",
                                 d, s);
         else
                 (void) snprintf(buf, l, "now");
@@ -501,7 +509,7 @@ char *format_timestamp_relative(char *buf, size_t l, usec_t t) {
 }
 #endif /* NM_IGNORED */
 
-char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
+char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
         static const struct {
                 const char *suffix;
                 usec_t usec;
@@ -608,7 +616,14 @@ char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) {
 }
 
 #if 0 /* NM_IGNORED */
-static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
+static int parse_timestamp_impl(
+                const char *t,
+                size_t tz_offset,
+                bool utc,
+                int isdst,
+                long gmtoff,
+                usec_t *ret) {
+
         static const struct {
                 const char *name;
                 const int nr;
@@ -629,12 +644,14 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
                 { "Sat",       6 },
         };
 
-        const char *k, *utc = NULL, *tzn = NULL;
+        _cleanup_free_ char *t_alloc = NULL;
+        usec_t usec, plus = 0, minus = 0;
+        bool with_tz = false;
+        int r, weekday = -1;
+        unsigned fractional = 0;
+        const char *k;
         struct tm tm, copy;
-        time_t x;
-        usec_t x_usec, plus = 0, minus = 0, ret;
-        int r, weekday = -1, dst = -1;
-        size_t i;
+        time_t sec;
 
         /* Allowed syntaxes:
          *
@@ -650,103 +667,96 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
          *   +5min
          *   -5days
          *   @2147483647          (seconds since epoch)
+         *
+         * Note, on DST change, 00:00:00 may not exist and in that case the time part may be shifted.
+         * E.g. "Sun 2023-03-13 America/Havana" is parsed as "Sun 2023-03-13 01:00:00 CDT".
          */
 
         assert(t);
 
+        if (tz_offset != SIZE_MAX) {
+                /* If the input string contains timezone, then cut it here. */
+
+                if (tz_offset <= 1) /* timezone must be after a space. */
+                        return -EINVAL;
+
+                t_alloc = strndup(t, tz_offset - 1);
+                if (!t_alloc)
+                        return -ENOMEM;
+
+                t = t_alloc;
+                with_tz = true;
+        }
+
+        if (utc) {
+                /* glibc accepts gmtoff more than 24 hours, but we refuse it. */
+                if ((usec_t) labs(gmtoff) * USEC_PER_SEC > USEC_PER_DAY)
+                        return -EINVAL;
+        } else {
+                if (gmtoff != 0)
+                        return -EINVAL;
+        }
+
         if (t[0] == '@' && !with_tz)
-                return parse_sec(t + 1, usec);
+                return parse_sec(t + 1, ret);
 
-        ret = now(CLOCK_REALTIME);
+        usec = now(CLOCK_REALTIME);
 
         if (!with_tz) {
                 if (streq(t, "now"))
                         goto finish;
 
-                else if (t[0] == '+') {
+                if (t[0] == '+') {
                         r = parse_sec(t+1, &plus);
                         if (r < 0)
                                 return r;
 
                         goto finish;
+                }
 
-                } else if (t[0] == '-') {
+                if (t[0] == '-') {
                         r = parse_sec(t+1, &minus);
                         if (r < 0)
                                 return r;
 
                         goto finish;
+                }
+
+                if ((k = endswith(t, " ago"))) {
+                        _cleanup_free_ char *buf = NULL;
 
-                } else if ((k = endswith(t, " ago"))) {
-                        t = strndupa_safe(t, k - t);
+                        buf = strndup(t, k - t);
+                        if (!buf)
+                                return -ENOMEM;
 
-                        r = parse_sec(t, &minus);
+                        r = parse_sec(buf, &minus);
                         if (r < 0)
                                 return r;
 
                         goto finish;
+                }
+
+                if ((k = endswith(t, " left"))) {
+                        _cleanup_free_ char *buf = NULL;
 
-                } else if ((k = endswith(t, " left"))) {
-                        t = strndupa_safe(t, k - t);
+                        buf = strndup(t, k - t);
+                        if (!buf)
+                                return -ENOMEM;
 
-                        r = parse_sec(t, &plus);
+                        r = parse_sec(buf, &plus);
                         if (r < 0)
                                 return r;
 
                         goto finish;
                 }
-
-                /* See if the timestamp is suffixed with UTC */
-                utc = endswith_no_case(t, " UTC");
-                if (utc)
-                        t = strndupa_safe(t, utc - t);
-                else {
-                        const char *e = NULL;
-                        int j;
-
-                        tzset();
-
-                        /* See if the timestamp is suffixed by either the DST or non-DST local timezone. Note
-                         * that we only support the local timezones here, nothing else. Not because we
-                         * wouldn't want to, but simply because there are no nice APIs available to cover
-                         * this. By accepting the local time zone strings, we make sure that all timestamps
-                         * written by format_timestamp() can be parsed correctly, even though we don't
-                         * support arbitrary timezone specifications. */
-
-                        for (j = 0; j <= 1; j++) {
-
-                                if (isempty(tzname[j]))
-                                        continue;
-
-                                e = endswith_no_case(t, tzname[j]);
-                                if (!e)
-                                        continue;
-                                if (e == t)
-                                        continue;
-                                if (e[-1] != ' ')
-                                        continue;
-
-                                break;
-                        }
-
-                        if (IN_SET(j, 0, 1)) {
-                                /* Found one of the two timezones specified. */
-                                t = strndupa_safe(t, e - t - 1);
-                                dst = j;
-                                tzn = tzname[j];
-                        }
-                }
         }
 
-        x = (time_t) (ret / USEC_PER_SEC);
-        x_usec = 0;
+        sec = (time_t) (usec / USEC_PER_SEC);
 
-        if (!localtime_or_gmtime_r(&x, &tm, utc))
+        if (!localtime_or_gmtime_r(&sec, &tm, utc))
                 return -EINVAL;
 
-        tm.tm_isdst = dst;
-        if (!with_tz && tzn)
-                tm.tm_zone = tzn;
+        tm.tm_isdst = isdst;
 
         if (streq(t, "today")) {
                 tm.tm_sec = tm.tm_min = tm.tm_hour = 0;
@@ -763,18 +773,13 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
                 goto from_tm;
         }
 
-        for (i = 0; i < ELEMENTSOF(day_nr); i++) {
-                size_t skip;
-
-                if (!startswith_no_case(t, day_nr[i].name))
-                        continue;
-
-                skip = strlen(day_nr[i].name);
-                if (t[skip] != ' ')
+        for (size_t i = 0; i < ELEMENTSOF(day_nr); i++) {
+                k = startswith_no_case(t, day_nr[i].name);
+                if (!k || *k != ' ')
                         continue;
 
                 weekday = day_nr[i].nr;
-                t += skip + 1;
+                t = k + 1;
                 break;
         }
 
@@ -853,65 +858,130 @@ static int parse_timestamp_impl(const char *t, usec_t *usec, bool with_tz) {
         return -EINVAL;
 
 parse_usec:
-        {
-                unsigned add;
-
-                k++;
-                r = parse_fractional_part_u(&k, 6, &add);
-                if (r < 0)
-                        return -EINVAL;
-
-                if (*k)
-                        return -EINVAL;
-
-                x_usec = add;
-        }
+        k++;
+        r = parse_fractional_part_u(&k, 6, &fractional);
+        if (r < 0)
+                return -EINVAL;
+        if (*k != '\0')
+                return -EINVAL;
 
 from_tm:
+        assert(plus == 0);
+        assert(minus == 0);
+
         if (weekday >= 0 && tm.tm_wday != weekday)
                 return -EINVAL;
 
-        x = mktime_or_timegm(&tm, utc);
-        if (x < 0)
-                return -EINVAL;
+        if (gmtoff < 0) {
+                plus = -gmtoff * USEC_PER_SEC;
+
+                /* If gmtoff is negative, the string may be too old to be parsed as UTC.
+                 * E.g. 1969-12-31 23:00:00 -06 == 1970-01-01 05:00:00 UTC
+                 * We assumed that gmtoff is in the range of -24:00…+24:00, hence the only date we need to
+                 * handle here is 1969-12-31. So, let's shift the date with one day, then subtract the shift
+                 * later. */
+                if (tm.tm_year == 69 && tm.tm_mon == 11 && tm.tm_mday == 31) {
+                        /* Thu 1970-01-01-00:00:00 */
+                        tm.tm_year = 70;
+                        tm.tm_mon = 0;
+                        tm.tm_mday = 1;
+                        tm.tm_wday = 4;
+                        tm.tm_yday = 0;
+                        minus = USEC_PER_DAY;
+                }
+        } else
+                minus = gmtoff * USEC_PER_SEC;
 
-        ret = (usec_t) x * USEC_PER_SEC + x_usec;
-        if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
+        sec = mktime_or_timegm(&tm, utc);
+        if (sec < 0)
                 return -EINVAL;
 
+        usec = usec_add(sec * USEC_PER_SEC, fractional);
+
 finish:
-        if (ret + plus < ret) /* overflow? */
-                return -EINVAL;
-        ret += plus;
-        if (ret > USEC_TIMESTAMP_FORMATTABLE_MAX)
+        usec = usec_add(usec, plus);
+
+        if (usec < minus)
                 return -EINVAL;
 
-        if (ret >= minus)
-                ret -= minus;
-        else
+        usec = usec_sub_unsigned(usec, minus);
+
+        if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
                 return -EINVAL;
 
-        if (usec)
-                *usec = ret;
+        if (ret)
+                *ret = usec;
         return 0;
 }
 
+static int parse_timestamp_maybe_with_tz(const char *t, size_t tz_offset, bool valid_tz, usec_t *ret) {
+        assert(t);
+
+        tzset();
+
+        for (int j = 0; j <= 1; j++) {
+                if (isempty(tzname[j]))
+                        continue;
+
+                if (!streq(t + tz_offset, tzname[j]))
+                        continue;
+
+                /* The specified timezone matches tzname[] of the local timezone. */
+                return parse_timestamp_impl(t, tz_offset, /* utc = */ false, /* isdst = */ j, /* gmtoff = */ 0, ret);
+        }
+
+        /* If we know that the last word is a valid timezone (e.g. Asia/Tokyo), then simply drop the timezone
+         * and parse the remaining string as a local time. If we know that the last word is not a timezone,
+         * then assume that it is a part of the time and try to parse the whole string as a local time. */
+        return parse_timestamp_impl(t, valid_tz ? tz_offset : SIZE_MAX,
+                                    /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+}
+
 typedef struct ParseTimestampResult {
         usec_t usec;
         int return_value;
 } ParseTimestampResult;
 
-int parse_timestamp(const char *t, usec_t *usec) {
-        char *last_space, *tz = NULL;
+int parse_timestamp(const char *t, usec_t *ret) {
         ParseTimestampResult *shared, tmp;
+        const char *k, *tz, *current_tz;
+        size_t tz_offset;
+        struct tm tm;
         int r;
 
-        last_space = strrchr(t, ' ');
-        if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG))
-                tz = last_space + 1;
+        assert(t);
+
+        tz = strrchr(t, ' ');
+        if (!tz)
+                return parse_timestamp_impl(t, /* tz_offset = */ SIZE_MAX, /* utc = */ false, /* isdst = */ -1, /* gmtoff = */ 0, ret);
+
+        tz++;
+        tz_offset = tz - t;
+
+        /* Shortcut, parse the string as UTC. */
+        if (streq(tz, "UTC"))
+                return parse_timestamp_impl(t, tz_offset, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ 0, ret);
 
-        if (!tz || endswith_no_case(t, " UTC"))
-                return parse_timestamp_impl(t, usec, false);
+        /* If the timezone is compatible with RFC-822/ISO 8601 (e.g. +06, or -03:00) then parse the string as
+         * UTC and shift the result. Note, this must be earlier than the timezone check with tzname[], as
+         * tzname[] may be in the same format. */
+        k = strptime(tz, "%z", &tm);
+        if (k && *k == '\0')
+                return parse_timestamp_impl(t, tz_offset, /* utc = */ true, /* isdst = */ -1, /* gmtoff = */ tm.tm_gmtoff, ret);
+
+        /* If the last word is not a timezone file (e.g. Asia/Tokyo), then let's check if it matches
+         * tzname[] of the local timezone, e.g. JST or CEST. */
+        if (!timezone_is_valid(tz, LOG_DEBUG))
+                return parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ false, ret);
+
+        /* Shortcut. If the current $TZ is equivalent to the specified timezone, it is not necessary to fork
+         * the process. */
+        current_tz = getenv("TZ");
+        if (current_tz && *current_tz == ':' && streq(current_tz + 1, tz))
+                return parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, ret);
+
+        /* Otherwise, to avoid polluting the current environment variables, let's fork the process and set
+         * the specified timezone in the child process. */
 
         shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
         if (shared == MAP_FAILED)
@@ -923,8 +993,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
                 return r;
         }
         if (r == 0) {
-                bool with_tz = true;
-                char *colon_tz;
+                const char *colon_tz;
 
                 /* tzset(3) says $TZ should be prefixed with ":" if we reference timezone files */
                 colon_tz = strjoina(":", tz);
@@ -934,17 +1003,7 @@ int parse_timestamp(const char *t, usec_t *usec) {
                         _exit(EXIT_FAILURE);
                 }
 
-                tzset();
-
-                /* If there is a timezone that matches the tzname fields, leave the parsing to the implementation.
-                 * Otherwise just cut it off. */
-                with_tz = !STR_IN_SET(tz, tzname[0], tzname[1]);
-
-                /* Cut off the timezone if we don't need it. */
-                if (with_tz)
-                        t = strndupa_safe(t, last_space - t);
-
-                shared->return_value = parse_timestamp_impl(t, &shared->usec, with_tz);
+                shared->return_value = parse_timestamp_maybe_with_tz(t, tz_offset, /* valid_tz = */ true, &shared->usec);
 
                 _exit(EXIT_SUCCESS);
         }
@@ -953,13 +1012,13 @@ int parse_timestamp(const char *t, usec_t *usec) {
         if (munmap(shared, sizeof *shared) != 0)
                 return negative_errno();
 
-        if (tmp.return_value == 0 && usec)
-                *usec = tmp.usec;
+        if (tmp.return_value == 0 && ret)
+                *ret = tmp.usec;
 
         return tmp.return_value;
 }
 
-static const char* extract_multiplier(const char *p, usec_t *multiplier) {
+static const char* extract_multiplier(const char *p, usec_t *ret) {
         static const struct {
                 const char *suffix;
                 usec_t usec;
@@ -995,12 +1054,15 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) {
                 { "µs",      1ULL            },
         };
 
+        assert(p);
+        assert(ret);
+
         for (size_t i = 0; i < ELEMENTSOF(table); i++) {
                 char *e;
 
                 e = startswith(p, table[i].suffix);
                 if (e) {
-                        *multiplier = table[i].usec;
+                        *ret = table[i].usec;
                         return e;
                 }
         }
@@ -1008,9 +1070,9 @@ static const char* extract_multiplier(const char *p, usec_t *multiplier) {
         return p;
 }
 
-int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
+int parse_time(const char *t, usec_t *ret, usec_t default_unit) {
         const char *p, *s;
-        usec_t r = 0;
+        usec_t usec = 0;
         bool something = false;
 
         assert(t);
@@ -1025,8 +1087,8 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                 if (*s != 0)
                         return -EINVAL;
 
-                if (usec)
-                        *usec = USEC_INFINITY;
+                if (ret)
+                        *ret = USEC_INFINITY;
                 return 0;
         }
 
@@ -1073,10 +1135,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                         return -ERANGE;
 
                 k = (usec_t) l * multiplier;
-                if (k >= USEC_INFINITY - r)
+                if (k >= USEC_INFINITY - usec)
                         return -ERANGE;
 
-                r += k;
+                usec += k;
 
                 something = true;
 
@@ -1086,10 +1148,10 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
 
                         for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
                                 k = (usec_t) (*b - '0') * m;
-                                if (k >= USEC_INFINITY - r)
+                                if (k >= USEC_INFINITY - usec)
                                         return -ERANGE;
 
-                                r += k;
+                                usec += k;
                         }
 
                         /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
@@ -1098,13 +1160,13 @@ int parse_time(const char *t, usec_t *usec, usec_t default_unit) {
                 }
         }
 
-        if (usec)
-                *usec = r;
+        if (ret)
+                *ret = usec;
         return 0;
 }
 
-int parse_sec(const char *t, usec_t *usec) {
-        return parse_time(t, usec, USEC_PER_SEC);
+int parse_sec(const char *t, usec_t *ret) {
+        return parse_time(t, ret, USEC_PER_SEC);
 }
 
 int parse_sec_fix_0(const char *t, usec_t *ret) {
@@ -1123,6 +1185,9 @@ int parse_sec_fix_0(const char *t, usec_t *ret) {
 }
 
 int parse_sec_def_infinity(const char *t, usec_t *ret) {
+        assert(t);
+        assert(ret);
+
         t += strspn(t, WHITESPACE);
         if (isempty(t)) {
                 *ret = USEC_INFINITY;
@@ -1131,7 +1196,7 @@ int parse_sec_def_infinity(const char *t, usec_t *ret) {
         return parse_sec(t, ret);
 }
 
-static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
+static const char* extract_nsec_multiplier(const char *p, nsec_t *ret) {
         static const struct {
                 const char *suffix;
                 nsec_t nsec;
@@ -1171,12 +1236,15 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
         };
         size_t i;
 
+        assert(p);
+        assert(ret);
+
         for (i = 0; i < ELEMENTSOF(table); i++) {
                 char *e;
 
                 e = startswith(p, table[i].suffix);
                 if (e) {
-                        *multiplier = table[i].nsec;
+                        *ret = table[i].nsec;
                         return e;
                 }
         }
@@ -1184,13 +1252,13 @@ static const char* extract_nsec_multiplier(const char *p, nsec_t *multiplier) {
         return p;
 }
 
-int parse_nsec(const char *t, nsec_t *nsec) {
+int parse_nsec(const char *t, nsec_t *ret) {
         const char *p, *s;
-        nsec_t r = 0;
+        nsec_t nsec = 0;
         bool something = false;
 
         assert(t);
-        assert(nsec);
+        assert(ret);
 
         p = t;
 
@@ -1201,7 +1269,7 @@ int parse_nsec(const char *t, nsec_t *nsec) {
                 if (*s != 0)
                         return -EINVAL;
 
-                *nsec = NSEC_INFINITY;
+                *ret = NSEC_INFINITY;
                 return 0;
         }
 
@@ -1248,10 +1316,10 @@ int parse_nsec(const char *t, nsec_t *nsec) {
                         return -ERANGE;
 
                 k = (nsec_t) l * multiplier;
-                if (k >= NSEC_INFINITY - r)
+                if (k >= NSEC_INFINITY - nsec)
                         return -ERANGE;
 
-                r += k;
+                nsec += k;
 
                 something = true;
 
@@ -1261,10 +1329,10 @@ int parse_nsec(const char *t, nsec_t *nsec) {
 
                         for (b = e + 1; *b >= '0' && *b <= '9'; b++, m /= 10) {
                                 k = (nsec_t) (*b - '0') * m;
-                                if (k >= NSEC_INFINITY - r)
+                                if (k >= NSEC_INFINITY - nsec)
                                         return -ERANGE;
 
-                                r += k;
+                                nsec += k;
                         }
 
                         /* Don't allow "0.-0", "3.+1", "3. 1", "3.sec" or "3.hoge" */
@@ -1273,7 +1341,7 @@ int parse_nsec(const char *t, nsec_t *nsec) {
                 }
         }
 
-        *nsec = r;
+        *ret = nsec;
 
         return 0;
 }
@@ -1324,6 +1392,8 @@ static int get_timezones_from_tzdata_zi(char ***ret) {
         _cleanup_strv_free_ char **zones = NULL;
         int r;
 
+        assert(ret);
+
         f = fopen("/usr/share/zoneinfo/tzdata.zi", "re");
         if (!f)
                 return -errno;
@@ -1401,7 +1471,7 @@ int get_timezones(char ***ret) {
 int verify_timezone(const char *name, int log_level) {
         bool slash = false;
         const char *p, *t;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         char buf[4];
         int r;
 
@@ -1483,6 +1553,8 @@ int get_timezone(char **ret) {
         char *z;
         int r;
 
+        assert(ret);
+
         r = readlink_malloc("/etc/localtime", &t);
         if (r == -ENOENT) {
                 /* If the symlink does not exist, assume "UTC", like glibc does */
@@ -1512,10 +1584,15 @@ int get_timezone(char **ret) {
 }
 
 time_t mktime_or_timegm(struct tm *tm, bool utc) {
+        assert(tm);
+
         return utc ? timegm(tm) : mktime(tm);
 }
 
 struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) {
+        assert(t);
+        assert(tm);
+
         return utc ? gmtime_r(t, tm) : localtime_r(t, tm);
 }
 
@@ -1575,7 +1652,7 @@ int time_change_fd(void) {
                 .it_value.tv_sec = TIME_T_MAX,
         };
 
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX));
 
@@ -1615,14 +1692,14 @@ int time_change_fd(void) {
 
 static const char* const timestamp_style_table[_TIMESTAMP_STYLE_MAX] = {
         [TIMESTAMP_PRETTY] = "pretty",
-        [TIMESTAMP_US] = "us",
-        [TIMESTAMP_UTC] = "utc",
+        [TIMESTAMP_US]     = "us",
+        [TIMESTAMP_UTC]    = "utc",
         [TIMESTAMP_US_UTC] = "us+utc",
-        [TIMESTAMP_UNIX] = "unix",
+        [TIMESTAMP_UNIX]   = "unix",
 };
 
 /* Use the macro for enum → string to allow for aliases */
-_DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle,);
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(timestamp_style, TimestampStyle);
 
 /* For the string → enum mapping we use the generic implementation, but also support two aliases */
 TimestampStyle timestamp_style_from_string(const char *s) {
diff --git a/src/libnm-systemd-shared/src/basic/time-util.h b/src/libnm-systemd-shared/src/basic/time-util.h
index c98f95a5..b49137d5 100644
--- a/src/libnm-systemd-shared/src/basic/time-util.h
+++ b/src/libnm-systemd-shared/src/basic/time-util.h
@@ -35,6 +35,7 @@ typedef enum TimestampStyle {
         TIMESTAMP_UTC,
         TIMESTAMP_US_UTC,
         TIMESTAMP_UNIX,
+        TIMESTAMP_DATE,
         _TIMESTAMP_STYLE_MAX,
         _TIMESTAMP_STYLE_INVALID = -EINVAL,
 } TimestampStyle;
@@ -65,7 +66,6 @@ typedef enum TimestampStyle {
 /* We assume a maximum timezone length of 6. TZNAME_MAX is not defined on Linux, but glibc internally initializes this
  * to 6. Let's rely on that. */
 #define FORMAT_TIMESTAMP_MAX (3U+1U+10U+1U+8U+1U+6U+1U+6U+1U)
-#define FORMAT_TIMESTAMP_WIDTH 28U /* when outputting, assume this width */
 #define FORMAT_TIMESTAMP_RELATIVE_MAX 256U
 #define FORMAT_TIMESPAN_MAX 64U
 
@@ -123,10 +123,19 @@ struct timeval* timeval_store(struct timeval *tv, usec_t u);
 #define TIMEVAL_STORE(u) timeval_store(&(struct timeval) {}, (u))
 
 char* format_timestamp_style(char *buf, size_t l, usec_t t, TimestampStyle style) _warn_unused_result_;
-char* format_timestamp_relative(char *buf, size_t l, usec_t t) _warn_unused_result_;
+char* format_timestamp_relative_full(char *buf, size_t l, usec_t t, clockid_t clock, bool implicit_left) _warn_unused_result_;
 char* format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) _warn_unused_result_;
 
 _warn_unused_result_
+static inline char* format_timestamp_relative(char *buf, size_t l, usec_t t) {
+        return format_timestamp_relative_full(buf, l, t, CLOCK_REALTIME, /* implicit_left = */ false);
+}
+_warn_unused_result_
+static inline char* format_timestamp_relative_monotonic(char *buf, size_t l, usec_t t) {
+        return format_timestamp_relative_full(buf, l, t, CLOCK_MONOTONIC, /* implicit_left = */ false);
+}
+
+_warn_unused_result_
 static inline char* format_timestamp(char *buf, size_t l, usec_t t) {
         return format_timestamp_style(buf, l, t, TIMESTAMP_PRETTY);
 }
@@ -137,19 +146,21 @@ static inline char* format_timestamp(char *buf, size_t l, usec_t t) {
 #define FORMAT_TIMESTAMP(t) format_timestamp((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t)
 #define FORMAT_TIMESTAMP_RELATIVE(t)                                    \
         format_timestamp_relative((char[FORMAT_TIMESTAMP_RELATIVE_MAX]){}, FORMAT_TIMESTAMP_RELATIVE_MAX, t)
+#define FORMAT_TIMESTAMP_RELATIVE_MONOTONIC(t)                          \
+        format_timestamp_relative_monotonic((char[FORMAT_TIMESTAMP_RELATIVE_MAX]){}, FORMAT_TIMESTAMP_RELATIVE_MAX, t)
 #define FORMAT_TIMESPAN(t, accuracy) format_timespan((char[FORMAT_TIMESPAN_MAX]){}, FORMAT_TIMESPAN_MAX, t, accuracy)
 #define FORMAT_TIMESTAMP_STYLE(t, style) \
         format_timestamp_style((char[FORMAT_TIMESTAMP_MAX]){}, FORMAT_TIMESTAMP_MAX, t, style)
 
-int parse_timestamp(const char *t, usec_t *usec);
+int parse_timestamp(const char *t, usec_t *ret);
 
-int parse_sec(const char *t, usec_t *usec);
-int parse_sec_fix_0(const char *t, usec_t *usec);
-int parse_sec_def_infinity(const char *t, usec_t *usec);
-int parse_time(const char *t, usec_t *usec, usec_t default_unit);
-int parse_nsec(const char *t, nsec_t *nsec);
+int parse_sec(const char *t, usec_t *ret);
+int parse_sec_fix_0(const char *t, usec_t *ret);
+int parse_sec_def_infinity(const char *t, usec_t *ret);
+int parse_time(const char *t, usec_t *ret, usec_t default_unit);
+int parse_nsec(const char *t, nsec_t *ret);
 
-int get_timezones(char ***l);
+int get_timezones(char ***ret);
 int verify_timezone(const char *name, int log_level);
 static inline bool timezone_is_valid(const char *name, int log_level) {
         return verify_timezone(name, log_level) >= 0;
@@ -159,7 +170,7 @@ bool clock_supported(clockid_t clock);
 
 usec_t usec_shift_clock(usec_t, clockid_t from, clockid_t to);
 
-int get_timezone(char **timezone);
+int get_timezone(char **ret);
 
 time_t mktime_or_timegm(struct tm *tm, bool utc);
 struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc);
@@ -200,13 +211,17 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
         return usec_sub_unsigned(timestamp, (usec_t) delta);
 }
 
+/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit
+ * year territory. However, since we want to stay away from this in all timezones we take one day off. */
+#define USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT ((usec_t) 253402214399000000) /* Thu 9999-12-30 23:59:59 UTC */
+/* With a 32bit time_t we can't go beyond 2038...
+ * We parse timestamp with RFC-822/ISO 8601 (e.g. +06, or -03:00) as UTC, hence the upper bound must be off
+ * by USEC_PER_DAY. See parse_timestamp() for more details. */
+#define USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT (((usec_t) INT32_MAX) * USEC_PER_SEC - USEC_PER_DAY)
 #if SIZEOF_TIME_T == 8
-  /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit
-   * year territory. However, since we want to stay away from this in all timezones we take one day off. */
-#  define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000)
+#  define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT
 #elif SIZEOF_TIME_T == 4
-/* With a 32bit time_t we can't go beyond 2038... */
-#  define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
+#  define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT
 #else
 #  error "Yuck, time_t is neither 4 nor 8 bytes wide?"
 #endif
diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.c b/src/libnm-systemd-shared/src/basic/tmpfile-util.c
index 2a27f86b..a66ee82d 100644
--- a/src/libnm-systemd-shared/src/basic/tmpfile-util.c
+++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.c
@@ -16,34 +16,22 @@
 #include "path-util.h"
 #include "process-util.h"
 #include "random-util.h"
+#include "stat-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
+#include "sync-util.h"
 #include "tmpfile-util.h"
 #include "umask-util.h"
 
-int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) {
+static int fopen_temporary_internal(int dir_fd, const char *path, FILE **ret_file) {
         _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_free_ char *t = NULL;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         int r;
 
-        if (path) {
-                r = tempfn_xxxxxx(path, NULL, &t);
-                if (r < 0)
-                        return r;
-        } else {
-                const char *d;
-
-                r = tmp_dir(&d);
-                if (r < 0)
-                        return r;
-
-                t = path_join(d, "XXXXXX");
-                if (!t)
-                        return -ENOMEM;
-        }
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
 
-        fd = mkostemp_safe(t);
+        fd = openat(dir_fd, path, O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL, 0600);
         if (fd < 0)
                 return -errno;
 
@@ -52,15 +40,59 @@ int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) {
 
         r = take_fdopen_unlocked(&fd, "w", &f);
         if (r < 0) {
-                (void) unlink(t);
+                (void) unlinkat(dir_fd, path, 0);
+                return r;
+        }
+
+        if (ret_file)
+                *ret_file = TAKE_PTR(f);
+
+        return 0;
+}
+
+int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+        assert(path);
+
+        r = tempfn_random(path, NULL, &t);
+        if (r < 0)
                 return r;
+
+        r = fopen_temporary_internal(dir_fd, t, ret_file);
+        if (r < 0)
+                return r;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(t);
+
+        return 0;
+}
+
+int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
+
+        if (!path) {
+                r = tmp_dir(&path);
+                if (r < 0)
+                        return r;
         }
 
-        if (ret_f)
-                *ret_f = TAKE_PTR(f);
+        r = tempfn_random_child(path, NULL, &t);
+        if (r < 0)
+                return r;
 
-        if (ret_temp_path)
-                *ret_temp_path = TAKE_PTR(t);
+        r = fopen_temporary_internal(dir_fd, t, ret_file);
+        if (r < 0)
+                return r;
+
+        if (ret_path)
+                *ret_path = TAKE_PTR(t);
 
         return 0;
 }
@@ -74,7 +106,7 @@ int mkostemp_safe(char *pattern) {
 
 #if 0 /* NM_IGNORED */
 int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
         FILE *f;
 
         fd = mkostemp_safe(pattern);
@@ -171,7 +203,6 @@ int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
         return tempfn_build(p, extra, "XXXXXX", /* child = */ false, ret);
 }
 
-#if 0 /* NM_IGNORED */
 int tempfn_random(const char *p, const char *extra, char **ret) {
         _cleanup_free_ char *s = NULL;
 
@@ -216,6 +247,7 @@ int tempfn_random_child(const char *p, const char *extra, char **ret) {
         return tempfn_build(p, extra, s, /* child = */ true, ret);
 }
 
+#if 0 /* NM_IGNORED */
 int open_tmpfile_unlinkable(const char *directory, int flags) {
         char *p;
         int fd, r;
@@ -246,7 +278,7 @@ int open_tmpfile_unlinkable(const char *directory, int flags) {
         return fd;
 }
 
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path) {
         _cleanup_free_ char *tmp = NULL;
         int r, fd;
 
@@ -260,7 +292,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
          * which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
          * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
 
-        fd = open_parent(target, O_TMPFILE|flags, 0640);
+        fd = open_parent_at(dir_fd, target, O_TMPFILE|flags, 0640);
         if (fd >= 0) {
                 *ret_path = NULL;
                 return fd;
@@ -272,7 +304,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
         if (r < 0)
                 return r;
 
-        fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
+        fd = openat(dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
         if (fd < 0)
                 return -errno;
 
@@ -284,7 +316,7 @@ int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file) {
         _cleanup_free_ char *path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
-        _cleanup_close_ int fd = -1;
+        _cleanup_close_ int fd = -EBADF;
 
         assert(target);
         assert(ret_file);
@@ -303,24 +335,84 @@ int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE
         return 0;
 }
 
-int link_tmpfile(int fd, const char *path, const char *target) {
+static int link_fd(int fd, int newdirfd, const char *newpath) {
+        int r;
+
+        assert(fd >= 0);
+        assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
+        assert(newpath);
+
+        /* Try symlinking via /proc/fd/ first. */
+        r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
+        if (r != -ENOENT)
+                return r;
+
+        /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
+         * more recent kernel, but does not require /proc/ mounted) */
+        if (proc_mounted() != 0)
+                return r;
+
+        return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
+}
+
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags) {
+        _cleanup_free_ char *tmp = NULL;
+        int r;
+
         assert(fd >= 0);
+        assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
         assert(target);
 
-        /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd
-         * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported
-         * on the directory, and renameat2() is used instead.
-         *
-         * Note that in both cases we will not replace existing files. This is because linkat() does not support this
-         * operation currently (renameat2() does), and there is no nice way to emulate this. */
+        /* Moves a temporary file created with open_tmpfile() above into its final place. If "path" is NULL
+         * an fd created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE
+         * is not supported on the directory, and renameat2() is used instead. */
+
+        if (FLAGS_SET(flags, LINK_TMPFILE_SYNC) && fsync(fd) < 0)
+                return -errno;
+
+        if (path) {
+                if (FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
+                        r = RET_NERRNO(renameat(dir_fd, path, dir_fd, target));
+                else
+                        r = rename_noreplace(dir_fd, path, dir_fd, target);
+                if (r < 0)
+                        return r;
+        } else {
+
+                r = link_fd(fd, dir_fd, target);
+                if (r != -EEXIST || !FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
+                        return r;
+
+                /* So the target already exists and we were asked to replace it. That sucks a bit, since the kernel's
+                 * linkat() logic does not allow that. We work-around this by linking the file to a random name
+                 * first, and then renaming that to the final name. This reintroduces the race O_TMPFILE kinda is
+                 * trying to fix, but at least the vulnerability window (i.e. where the file is linked into the file
+                 * system under a temporary name) is very short. */
+
+                r = tempfn_random(target, NULL, &tmp);
+                if (r < 0)
+                        return r;
+
+                if (link_fd(fd, dir_fd, tmp) < 0)
+                        return -EEXIST; /* propagate original error */
+
+                r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target));
+                if (r < 0) {
+                        (void) unlinkat(dir_fd, tmp, 0);
+                        return r;
+                }
+        }
 
-        if (path)
-                return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
+        if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) {
+                r = fsync_full(fd);
+                if (r < 0)
+                        return r;
+        }
 
-        return RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), AT_FDCWD, target, AT_SYMLINK_FOLLOW));
+        return 0;
 }
 
-int flink_tmpfile(FILE *f, const char *path, const char *target) {
+int flink_tmpfile(FILE *f, const char *path, const char *target, LinkTmpfileFlags flags) {
         int fd, r;
 
         assert(f);
@@ -330,11 +422,11 @@ int flink_tmpfile(FILE *f, const char *path, const char *target) {
         if (fd < 0) /* Not all FILE* objects encapsulate fds */
                 return -EBADF;
 
-        r = fflush_sync_and_check(f);
+        r = fflush_and_check(f);
         if (r < 0)
                 return r;
 
-        return link_tmpfile(fd, path, target);
+        return link_tmpfile(fd, path, target, flags);
 }
 
 int mkdtemp_malloc(const char *template, char **ret) {
@@ -363,4 +455,24 @@ int mkdtemp_malloc(const char *template, char **ret) {
         *ret = TAKE_PTR(p);
         return 0;
 }
+
+int mkdtemp_open(const char *template, int flags, char **ret) {
+        _cleanup_free_ char *p = NULL;
+        int fd, r;
+
+        r = mkdtemp_malloc(template, &p);
+        if (r < 0)
+                return r;
+
+        fd = RET_NERRNO(open(p, O_DIRECTORY|O_CLOEXEC|flags));
+        if (fd < 0) {
+                (void) rmdir(p);
+                return fd;
+        }
+
+        if (ret)
+                *ret = TAKE_PTR(p);
+
+        return fd;
+}
 #endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/tmpfile-util.h b/src/libnm-systemd-shared/src/basic/tmpfile-util.h
index 610cbaf8..50904eca 100644
--- a/src/libnm-systemd-shared/src/basic/tmpfile-util.h
+++ b/src/libnm-systemd-shared/src/basic/tmpfile-util.h
@@ -1,9 +1,20 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
+#include <fcntl.h>
+#include <stdbool.h>
 #include <stdio.h>
 
-int fopen_temporary(const char *path, FILE **_f, char **_temp_path);
+int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path);
+static inline int fopen_temporary(const char *path, FILE **ret_file, char **ret_path) {
+        return fopen_temporary_at(AT_FDCWD, path, ret_file, ret_path);
+}
+
+int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path);
+static inline int fopen_temporary_child(const char *path, FILE **ret_file, char **ret_path) {
+        return fopen_temporary_child_at(AT_FDCWD, path, ret_file, ret_path);
+}
+
 int mkostemp_safe(char *pattern);
 int fmkostemp_safe(char *pattern, const char *mode, FILE**_f);
 
@@ -12,10 +23,23 @@ int tempfn_random(const char *p, const char *extra, char **ret);
 int tempfn_random_child(const char *p, const char *extra, char **ret);
 
 int open_tmpfile_unlinkable(const char *directory, int flags);
-int open_tmpfile_linkable(const char *target, int flags, char **ret_path);
+int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path);
+static inline int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
+        return open_tmpfile_linkable_at(AT_FDCWD, target, flags, ret_path);
+}
 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file);
 
-int link_tmpfile(int fd, const char *path, const char *target);
-int flink_tmpfile(FILE *f, const char *path, const char *target);
+
+typedef enum LinkTmpfileFlags {
+        LINK_TMPFILE_REPLACE = 1 << 0,
+        LINK_TMPFILE_SYNC    = 1 << 1,
+} LinkTmpfileFlags;
+
+int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags);
+static inline int link_tmpfile(int fd, const char *path, const char *target, LinkTmpfileFlags flags) {
+        return link_tmpfile_at(fd, AT_FDCWD, path, target, flags);
+}
+int flink_tmpfile(FILE *f, const char *path, const char *target, LinkTmpfileFlags flags);
 
 int mkdtemp_malloc(const char *template, char **ret);
+int mkdtemp_open(const char *template, int flags, char **ret);
diff --git a/src/libnm-systemd-shared/src/basic/umask-util.h b/src/libnm-systemd-shared/src/basic/umask-util.h
index 90d18f70..6f0e1cc2 100644
--- a/src/libnm-systemd-shared/src/basic/umask-util.h
+++ b/src/libnm-systemd-shared/src/basic/umask-util.h
@@ -15,12 +15,12 @@ static inline void umaskp(mode_t *u) {
 
 /* We make use of the fact here that the umask() concept is using only the lower 9 bits of mode_t, although
  * mode_t has space for the file type in the bits further up. We simply OR in the file type mask S_IFMT to
- * distinguish the first and the second iteration of the RUN_WITH_UMASK() loop, so that we can run the first
- * one, and exit on the second. */
+ * distinguish the first and the second iteration of the WITH_UMASK() loop, so that we can run the first one,
+ * and exit on the second. */
 
 assert_cc((S_IFMT & 0777) == 0);
 
-#define RUN_WITH_UMASK(mask)                                            \
+#define WITH_UMASK(mask)                                            \
         for (_cleanup_umask_ mode_t _saved_umask_ = umask(mask) | S_IFMT; \
              FLAGS_SET(_saved_umask_, S_IFMT);                          \
              _saved_umask_ &= 0777)
diff --git a/src/libnm-systemd-shared/src/basic/user-util.h b/src/libnm-systemd-shared/src/basic/user-util.h
index a08683bc..8b829a9a 100644
--- a/src/libnm-systemd-shared/src/basic/user-util.h
+++ b/src/libnm-systemd-shared/src/basic/user-util.h
@@ -80,7 +80,8 @@ int take_etc_passwd_lock(const char *root);
 #define UID_MAPPED_ROOT ((uid_t) (INT32_MAX-1))
 #define GID_MAPPED_ROOT ((gid_t) (INT32_MAX-1))
 
-#define ETC_PASSWD_LOCK_PATH "/etc/.pwd.lock"
+#define ETC_PASSWD_LOCK_FILENAME ".pwd.lock"
+#define ETC_PASSWD_LOCK_PATH "/etc/" ETC_PASSWD_LOCK_FILENAME
 
 /* The following macros add 1 when converting things, since UID 0 is a valid UID, while the pointer
  * NULL is special */
@@ -102,7 +103,7 @@ typedef enum ValidUserFlags {
 
 bool valid_user_group_name(const char *u, ValidUserFlags flags);
 bool valid_gecos(const char *d);
-char *mangle_gecos(const char *d);
+char* mangle_gecos(const char *d);
 bool valid_home(const char *p);
 
 static inline bool valid_shell(const char *p) {
@@ -130,11 +131,12 @@ int putsgent_sane(const struct sgrp *sg, FILE *stream);
 #endif
 
 bool is_nologin_shell(const char *shell);
+const char* default_root_shell_at(int rfd);
 const char* default_root_shell(const char *root);
 
 int is_this_me(const char *username);
 
-const char *get_home_root(void);
+const char* get_home_root(void);
 
 static inline bool hashed_password_is_locked_or_invalid(const char *password) {
         return password && password[0] != '$';
@@ -148,3 +150,8 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) {
 
 /* A password indicating "hey, no password required for login" */
 #define PASSWORD_NONE ""
+
+/* Used by sysusers to indicate that the password should be filled in by firstboot.
+ * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325.
+ */
+#define PASSWORD_UNPROVISIONED "!unprovisioned"
diff --git a/src/libnm-systemd-shared/src/basic/util.c b/src/libnm-systemd-shared/src/basic/util.c
deleted file mode 100644
index 3425d0f0..00000000
--- a/src/libnm-systemd-shared/src/basic/util.c
+++ /dev/null
@@ -1,178 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-
-#include "nm-sd-adapt-shared.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/mman.h>
-
-#include "alloc-util.h"
-#include "build.h"
-#include "env-file.h"
-#include "env-util.h"
-#include "fd-util.h"
-#include "fileio.h"
-#include "hostname-util.h"
-#include "log.h"
-#include "macro.h"
-#include "parse-util.h"
-#include "stat-util.h"
-#include "string-util.h"
-#include "util.h"
-#include "virt.h"
-
-#if 0 /* NM_IGNORED */
-int saved_argc = 0;
-char **saved_argv = NULL;
-static int saved_in_initrd = -1;
-
-bool kexec_loaded(void) {
-       _cleanup_free_ char *s = NULL;
-
-       if (read_one_line_file("/sys/kernel/kexec_loaded", &s) < 0)
-               return false;
-
-       return s[0] == '1';
-}
-
-int prot_from_flags(int flags) {
-
-        switch (flags & O_ACCMODE) {
-
-        case O_RDONLY:
-                return PROT_READ;
-
-        case O_WRONLY:
-                return PROT_WRITE;
-
-        case O_RDWR:
-                return PROT_READ|PROT_WRITE;
-
-        default:
-                return -EINVAL;
-        }
-}
-
-bool in_initrd(void) {
-        int r;
-        const char *e;
-        bool lenient = false;
-
-        if (saved_in_initrd >= 0)
-                return saved_in_initrd;
-
-        /* We have two checks here:
-         *
-         * 1. the flag file /etc/initrd-release must exist
-         * 2. the root file system must be a memory file system
-         *
-         * The second check is extra paranoia, since misdetecting an
-         * initrd can have bad consequences due the initrd
-         * emptying when transititioning to the main systemd.
-         *
-         * If env var $SYSTEMD_IN_INITRD is not set or set to "auto",
-         * both checks are used. If it's set to "lenient", only check
-         * 1 is used. If set to a boolean value, then the boolean
-         * value is returned.
-         */
-
-        e = secure_getenv("SYSTEMD_IN_INITRD");
-        if (e) {
-                if (streq(e, "lenient"))
-                        lenient = true;
-                else if (!streq(e, "auto")) {
-                        r = parse_boolean(e);
-                        if (r >= 0) {
-                                saved_in_initrd = r > 0;
-                                return saved_in_initrd;
-                        }
-                        log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m");
-                }
-        }
-
-        if (!lenient) {
-                r = path_is_temporary_fs("/");
-                if (r < 0)
-                        log_debug_errno(r, "Couldn't determine if / is a temporary file system: %m");
-
-                saved_in_initrd = r > 0;
-        }
-
-        r = access("/etc/initrd-release", F_OK);
-        if (r >= 0) {
-                if (saved_in_initrd == 0)
-                        log_debug("/etc/initrd-release exists, but it's not an initrd.");
-                else
-                        saved_in_initrd = 1;
-        } else {
-                if (errno != ENOENT)
-                        log_debug_errno(errno, "Failed to test if /etc/initrd-release exists: %m");
-                saved_in_initrd = 0;
-        }
-
-        return saved_in_initrd;
-}
-
-void in_initrd_force(bool value) {
-        saved_in_initrd = value;
-}
-
-int container_get_leader(const char *machine, pid_t *pid) {
-        _cleanup_free_ char *s = NULL, *class = NULL;
-        const char *p;
-        pid_t leader;
-        int r;
-
-        assert(machine);
-        assert(pid);
-
-        if (streq(machine, ".host")) {
-                *pid = 1;
-                return 0;
-        }
-
-        if (!hostname_is_valid(machine, 0))
-                return -EINVAL;
-
-        p = strjoina("/run/systemd/machines/", machine);
-        r = parse_env_file(NULL, p,
-                           "LEADER", &s,
-                           "CLASS", &class);
-        if (r == -ENOENT)
-                return -EHOSTDOWN;
-        if (r < 0)
-                return r;
-        if (!s)
-                return -EIO;
-
-        if (!streq_ptr(class, "container"))
-                return -EIO;
-
-        r = parse_pid(s, &leader);
-        if (r < 0)
-                return r;
-        if (leader <= 1)
-                return -EIO;
-
-        *pid = leader;
-        return 0;
-}
-
-int version(void) {
-        printf("systemd " STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")\n%s\n",
-               systemd_features);
-        return 0;
-}
-
-/* Turn off core dumps but only if we're running outside of a container. */
-void disable_coredumps(void) {
-        int r;
-
-        if (detect_container() > 0)
-                return;
-
-        r = write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", WRITE_STRING_FILE_DISABLE_BUFFER);
-        if (r < 0)
-                log_debug_errno(r, "Failed to turn off coredumps, ignoring: %m");
-}
-#endif /* NM_IGNORED */
diff --git a/src/libnm-systemd-shared/src/basic/util.h b/src/libnm-systemd-shared/src/fundamental/logarithm.h
index 368e3e4f..5f742951 100644
--- a/src/libnm-systemd-shared/src/basic/util.h
+++ b/src/libnm-systemd-shared/src/fundamental/logarithm.h
@@ -3,29 +3,6 @@
 
 #include <stdint.h>
 
-#include "macro.h"
-
-extern int saved_argc;
-extern char **saved_argv;
-
-static inline void save_argc_argv(int argc, char **argv) {
-
-        /* Protect against CVE-2021-4034 style attacks */
-        assert_se(argc > 0);
-        assert_se(argv);
-        assert_se(argv[0]);
-
-        saved_argc = argc;
-        saved_argv = argv;
-}
-
-bool kexec_loaded(void);
-
-int prot_from_flags(int flags) _const_;
-
-bool in_initrd(void);
-void in_initrd_force(bool value);
-
 /* Note: log2(0) == log2(1) == 0 here and below. */
 
 #define CONST_LOG2ULL(x) ((x) > 1 ? (unsigned) __builtin_clzll(x) ^ 63U : 0)
@@ -55,6 +32,14 @@ static inline unsigned u32ctz(uint32_t n) {
 #endif
 }
 
+#define popcount(n)                                             \
+        _Generic((n),                                           \
+                 unsigned char: __builtin_popcount(n),          \
+                 unsigned short: __builtin_popcount(n),         \
+                 unsigned: __builtin_popcount(n),               \
+                 unsigned long: __builtin_popcountl(n),         \
+                 unsigned long long: __builtin_popcountll(n))
+
 #define CONST_LOG2U(x) ((x) > 1 ? __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1 : 0)
 #define NONCONST_LOG2U(x) ({                                             \
                 unsigned _x = (x);                                       \
@@ -80,9 +65,3 @@ static inline unsigned log2u_round_up(unsigned x) {
 
         return log2u(x - 1) + 1;
 }
-
-int container_get_leader(const char *machine, pid_t *pid);
-
-int version(void);
-
-void disable_coredumps(void);
diff --git a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
index 0c69d98a..89b83e7d 100644
--- a/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
+++ b/src/libnm-systemd-shared/src/fundamental/macro-fundamental.h
@@ -1,17 +1,18 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#ifndef SD_BOOT
+#if !SD_BOOT
 #  include <assert.h>
 #endif
 
 #include <limits.h>
+#include <stdalign.h>
 #include <stdbool.h>
 #include <stddef.h>
 #include <stdint.h>
 
 #define _align_(x) __attribute__((__aligned__(x)))
-#define _alignas_(x) __attribute__((__aligned__(__alignof__(x))))
+#define _alignas_(x) __attribute__((__aligned__(alignof(x))))
 #define _alignptr_ __attribute__((__aligned__(sizeof(void *))))
 #define _cleanup_(x) __attribute__((__cleanup__(x)))
 #define _const_ __attribute__((__const__))
@@ -20,6 +21,7 @@
 #define _hidden_ __attribute__((__visibility__("hidden")))
 #define _likely_(x) (__builtin_expect(!!(x), 1))
 #define _malloc_ __attribute__((__malloc__))
+#define _noinline_ __attribute__((noinline))
 #define _noreturn_ _Noreturn
 #define _packed_ __attribute__((__packed__))
 #define _printf_(a, b) __attribute__((__format__(printf, a, b)))
@@ -36,6 +38,13 @@
 #define _weak_ __attribute__((__weak__))
 #define _weakref_(x) __attribute__((__weakref__(#x)))
 
+/* !NM_IGNORED */
+#if __GNUC__ <= 4
+#undef _returns_nonnull_
+#define _returns_nonnull_
+#endif
+/* !NM_IGNORED */
+
 #ifdef __clang__
 #  define _alloc_(...)
 #else
@@ -66,18 +75,18 @@
 #define XCONCATENATE(x, y) x ## y
 #define CONCATENATE(x, y) XCONCATENATE(x, y)
 
-#ifdef SD_BOOT
+#if SD_BOOT
         _noreturn_ void efi_assert(const char *expr, const char *file, unsigned line, const char *function);
 
         #ifdef NDEBUG
                 #define assert(expr)
                 #define assert_not_reached() __builtin_unreachable()
         #else
-                #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); })
-                #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __PRETTY_FUNCTION__)
+                #define assert(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); })
+                #define assert_not_reached() efi_assert("Code should not be reached", __FILE__, __LINE__, __func__)
         #endif
         #define static_assert _Static_assert
-        #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __PRETTY_FUNCTION__); })
+        #define assert_se(expr) ({ _likely_(expr) ? VOID_0 : efi_assert(#expr, __FILE__, __LINE__, __func__); })
 #endif
 
 /* This passes the argument through after (if asserts are enabled) checking that it is not null. */
@@ -106,7 +115,6 @@
 
 #define assert_cc(expr) static_assert(expr, #expr)
 
-
 #define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq))
 #define UNIQ __COUNTER__
 
@@ -259,47 +267,54 @@
                 (UNIQ_T(X, xq) / UNIQ_T(Y, yq) + !!(UNIQ_T(X, xq) % UNIQ_T(Y, yq))); \
         })
 
-#define CASE_F(X) case X:
-#define CASE_F_1(CASE, X) CASE_F(X)
-#define CASE_F_2(CASE, X, ...)  CASE(X) CASE_F_1(CASE, __VA_ARGS__)
-#define CASE_F_3(CASE, X, ...)  CASE(X) CASE_F_2(CASE, __VA_ARGS__)
-#define CASE_F_4(CASE, X, ...)  CASE(X) CASE_F_3(CASE, __VA_ARGS__)
-#define CASE_F_5(CASE, X, ...)  CASE(X) CASE_F_4(CASE, __VA_ARGS__)
-#define CASE_F_6(CASE, X, ...)  CASE(X) CASE_F_5(CASE, __VA_ARGS__)
-#define CASE_F_7(CASE, X, ...)  CASE(X) CASE_F_6(CASE, __VA_ARGS__)
-#define CASE_F_8(CASE, X, ...)  CASE(X) CASE_F_7(CASE, __VA_ARGS__)
-#define CASE_F_9(CASE, X, ...)  CASE(X) CASE_F_8(CASE, __VA_ARGS__)
-#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__)
-#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__)
-#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__)
-#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__)
-#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__)
-#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__)
-#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__)
-#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__)
-#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__)
-#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__)
-#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__)
+/* Rounds up x to the next multiple of y. Resolves to typeof(x) -1 in case of overflow */
+#define __ROUND_UP(q, x, y)                                             \
+        ({                                                              \
+                const typeof(y) UNIQ_T(A, q) = (y);                     \
+                const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP((x), UNIQ_T(A, q)); \
+                typeof(x) UNIQ_T(C, q);                                 \
+                __builtin_mul_overflow(UNIQ_T(B, q), UNIQ_T(A, q), &UNIQ_T(C, q)) ? (typeof(x)) -1 : UNIQ_T(C, q); \
+        })
+#define ROUND_UP(x, y) __ROUND_UP(UNIQ, (x), (y))
+
+#define  CASE_F_1(X)      case X:
+#define  CASE_F_2(X, ...) case X:  CASE_F_1( __VA_ARGS__)
+#define  CASE_F_3(X, ...) case X:  CASE_F_2( __VA_ARGS__)
+#define  CASE_F_4(X, ...) case X:  CASE_F_3( __VA_ARGS__)
+#define  CASE_F_5(X, ...) case X:  CASE_F_4( __VA_ARGS__)
+#define  CASE_F_6(X, ...) case X:  CASE_F_5( __VA_ARGS__)
+#define  CASE_F_7(X, ...) case X:  CASE_F_6( __VA_ARGS__)
+#define  CASE_F_8(X, ...) case X:  CASE_F_7( __VA_ARGS__)
+#define  CASE_F_9(X, ...) case X:  CASE_F_8( __VA_ARGS__)
+#define CASE_F_10(X, ...) case X:  CASE_F_9( __VA_ARGS__)
+#define CASE_F_11(X, ...) case X: CASE_F_10( __VA_ARGS__)
+#define CASE_F_12(X, ...) case X: CASE_F_11( __VA_ARGS__)
+#define CASE_F_13(X, ...) case X: CASE_F_12( __VA_ARGS__)
+#define CASE_F_14(X, ...) case X: CASE_F_13( __VA_ARGS__)
+#define CASE_F_15(X, ...) case X: CASE_F_14( __VA_ARGS__)
+#define CASE_F_16(X, ...) case X: CASE_F_15( __VA_ARGS__)
+#define CASE_F_17(X, ...) case X: CASE_F_16( __VA_ARGS__)
+#define CASE_F_18(X, ...) case X: CASE_F_17( __VA_ARGS__)
+#define CASE_F_19(X, ...) case X: CASE_F_18( __VA_ARGS__)
+#define CASE_F_20(X, ...) case X: CASE_F_19( __VA_ARGS__)
 
 #define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME
 #define FOR_EACH_MAKE_CASE(...) \
         GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \
                                CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
-                   (CASE_F,__VA_ARGS__)
+                   (__VA_ARGS__)
 
-#define IN_SET(x, ...)                                                  \
+#define IN_SET(x, first, ...)                                           \
         ({                                                              \
                 bool _found = false;                                    \
-                /* If the build breaks in the line below, you need to extend the case macros. (We use "long double" as  \
-                 * type for the array, in the hope that checkers such as ubsan don't complain that the initializers for \
-                 * the array are not representable by the base type. Ideally we'd use typeof(x) as base type, but that  \
-                 * doesn't work, as we want to use this on bitfields and gcc refuses typeof() on bitfields.) */         \
-                static const long double __assert_in_set[] _unused_ = { __VA_ARGS__ }; \
+                /* If the build breaks in the line below, you need to extend the case macros. We use typeof(+x) \
+                 * here to widen the type of x if it is a bit-field as this would otherwise be illegal. */      \
+                static const typeof(+x) __assert_in_set[] _unused_ = { first, __VA_ARGS__ }; \
                 assert_cc(ELEMENTSOF(__assert_in_set) <= 20);           \
                 switch (x) {                                            \
-                FOR_EACH_MAKE_CASE(__VA_ARGS__)                         \
+                FOR_EACH_MAKE_CASE(first, __VA_ARGS__)                  \
                         _found = true;                                  \
-                       break;                                           \
+                        break;                                          \
                 default:                                                \
                         break;                                          \
                 }                                                       \
@@ -308,13 +323,18 @@
 
 /* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time
  * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */
-#define TAKE_PTR(ptr)                           \
-        ({                                      \
-                typeof(ptr) *_pptr_ = &(ptr);   \
-                typeof(ptr) _ptr_ = *_pptr_;    \
-                *_pptr_ = NULL;                 \
-                _ptr_;                          \
+#define TAKE_GENERIC(var, type, nullvalue)                       \
+        ({                                                       \
+                type *_pvar_ = &(var);                           \
+                type _var_ = *_pvar_;                            \
+                type _nullvalue_ = nullvalue;                    \
+                *_pvar_ = _nullvalue_;                           \
+                _var_;                                           \
         })
+#define TAKE_PTR_TYPE(ptr, type) TAKE_GENERIC(ptr, type, NULL)
+#define TAKE_PTR(ptr) TAKE_PTR_TYPE(ptr, typeof(ptr))
+#define TAKE_STRUCT_TYPE(s, type) TAKE_GENERIC(s, type, {})
+#define TAKE_STRUCT(s) TAKE_STRUCT_TYPE(s, typeof(s))
 
 /*
  * STRLEN - return the length of a string literal, minus the trailing NUL byte.
@@ -338,13 +358,19 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
         return ((l + ali - 1) & ~(ali - 1));
 }
 
+#define ALIGN2(l) ALIGN_TO(l, 2)
 #define ALIGN4(l) ALIGN_TO(l, 4)
 #define ALIGN8(l) ALIGN_TO(l, 8)
-#ifndef SD_BOOT
-/* libefi also provides ALIGN, and we do not use them in sd-boot explicitly. */
+#define ALIGN2_PTR(p) ((void*) ALIGN2((uintptr_t) p))
+#define ALIGN4_PTR(p) ((void*) ALIGN4((uintptr_t) p))
+#define ALIGN8_PTR(p) ((void*) ALIGN8((uintptr_t) p))
 #define ALIGN(l)  ALIGN_TO(l, sizeof(void*))
 #define ALIGN_PTR(p) ((void*) ALIGN((uintptr_t) (p)))
-#endif
+
+/* Checks if the specified pointer is aligned as appropriate for the specific type */
+#define IS_ALIGNED16(p) (((uintptr_t) p) % alignof(uint16_t) == 0)
+#define IS_ALIGNED32(p) (((uintptr_t) p) % alignof(uint32_t) == 0)
+#define IS_ALIGNED64(p) (((uintptr_t) p) % alignof(uint64_t) == 0)
 
 /* Same as ALIGN_TO but callable in constant contexts. */
 #define CONST_ALIGN_TO(l, ali)                                         \
@@ -356,9 +382,31 @@ static inline size_t ALIGN_TO(size_t l, size_t ali) {
                 ((l) + (ali) - 1) & ~((ali) - 1),                      \
                 VOID_0)
 
+/* Similar to ((t *) (void *) (p)) to cast a pointer. The macro asserts that the pointer has a suitable
+ * alignment for type "t". This exists for places where otherwise "-Wcast-align=strict" would issue a
+ * warning or if you want to assert that the cast gives a pointer of suitable alignment. */
+#define CAST_ALIGN_PTR(t, p)                                    \
+        ({                                                      \
+                const void *_p = (p);                           \
+                assert(((uintptr_t) _p) % alignof(t) == 0); \
+                (t *) _p;                                       \
+        })
+
 #define UPDATE_FLAG(orig, flag, b)                      \
         ((b) ? ((orig) | (flag)) : ((orig) & ~(flag)))
 #define SET_FLAG(v, flag, b) \
         (v) = UPDATE_FLAG(v, flag, b)
 #define FLAGS_SET(v, flags) \
         ((~(v) & (flags)) == 0)
+
+/* Declare a flexible array usable in a union.
+ * This is essentially a work-around for a pointless constraint in C99
+ * and might go away in some future version of the standard.
+ *
+ * See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=3080ea5553cc909b000d1f1d964a9041962f2c5b
+ */
+#define DECLARE_FLEX_ARRAY(type, name)                 \
+        struct {                                       \
+                dummy_t __empty__ ## name;             \
+                type name[];                           \
+        }
diff --git a/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h
new file mode 100644
index 00000000..78e2dbec
--- /dev/null
+++ b/src/libnm-systemd-shared/src/fundamental/memory-util-fundamental.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <stddef.h>
+
+#if SD_BOOT
+#  include "efi-string.h"
+#else
+#  include <string.h>
+#endif
+
+#include "macro-fundamental.h"
+
+#if !SD_BOOT && HAVE_EXPLICIT_BZERO
+static inline void *explicit_bzero_safe(void *p, size_t l) {
+        if (p && l > 0)
+                explicit_bzero(p, l);
+
+        return p;
+}
+#else
+static inline void *explicit_bzero_safe(void *p, size_t l) {
+        if (p && l > 0) {
+                memset(p, 0, l);
+                __asm__ __volatile__("" : : "r"(p) : "memory");
+        }
+        return p;
+}
+#endif
+
+struct VarEraser {
+        /* NB: This is a pointer to memory to erase in case of CLEANUP_ERASE(). Pointer to pointer to memory
+         * to erase in case of CLEANUP_ERASE_PTR() */
+        void *p;
+        size_t size;
+};
+
+static inline void erase_var(struct VarEraser *e) {
+        explicit_bzero_safe(e->p, e->size);
+}
+
+/* Mark var to be erased when leaving scope. */
+#define CLEANUP_ERASE(var)                                              \
+        _cleanup_(erase_var) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \
+                .p = &(var),                                            \
+                .size = sizeof(var),                                    \
+        }
+
+static inline void erase_varp(struct VarEraser *e) {
+
+        /* Very similar to erase_var(), but assumes `p` is a pointer to a pointer whose memory shall be destructed. */
+        if (!e->p)
+                return;
+
+        explicit_bzero_safe(*(void**) e->p, e->size);
+}
+
+/* Mark pointer so that memory pointed to is erased when leaving scope. Note: this takes a pointer to the
+ * specified pointer, instead of just a copy of it. This is to allow callers to invalidate the pointer after
+ * use, if they like, disabling our automatic erasure (for example because they succeeded with whatever they
+ * wanted to do and now intend to return the allocated buffer to their caller without it being erased). */
+#define CLEANUP_ERASE_PTR(ptr, sz)                                      \
+        _cleanup_(erase_varp) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { \
+                .p = (ptr),                                             \
+                .size = (sz),                                           \
+        }
diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.c b/src/libnm-systemd-shared/src/fundamental/sha256.c
index b4c36ab3..a4c6d627 100644
--- a/src/libnm-systemd-shared/src/fundamental/sha256.c
+++ b/src/libnm-systemd-shared/src/fundamental/sha256.c
@@ -24,7 +24,7 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <stdbool.h>
-#ifdef SD_BOOT
+#if SD_BOOT
 #  include "efi-string.h"
 #else
 #  include <string.h>
@@ -32,6 +32,7 @@
 
 #include "macro-fundamental.h"
 #include "sha256.h"
+#include "unaligned-fundamental.h"
 
 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
 # define SWAP(n)                                                        \
@@ -50,14 +51,6 @@
 # define SWAP64(n) (n)
 #endif
 
-/* The condition below is from glibc's string/string-inline.c.
- * See definition of _STRING_INLINE_unaligned. */
-#if !defined(__mc68020__) && !defined(__s390__) && !defined(__i386__)
-#  define UNALIGNED_P(p) (((uintptr_t) p) % __alignof__(uint32_t) != 0)
-#else
-#  define UNALIGNED_P(p) false
-#endif
-
 /* This array contains the bytes used to pad the buffer to the next
    64-byte boundary.  (FIPS 180-2:5.1.1)  */
 static const uint8_t fillbuf[64] = {
@@ -130,11 +123,7 @@ uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_
 
         /* Put result from CTX in first 32 bytes following RESBUF.  */
         for (size_t i = 0; i < 8; ++i)
-                if (UNALIGNED_P(resbuf))
-                        memcpy(resbuf + i * sizeof(uint32_t), (uint32_t[]) { SWAP(ctx->H[i]) }, sizeof(uint32_t));
-                else
-                        ((uint32_t *) (void *) resbuf)[i] = SWAP(ctx->H[i]);
-
+                unaligned_write_ne32(resbuf + i * sizeof(uint32_t), SWAP(ctx->H[i]));
         return resbuf;
 }
 
@@ -167,18 +156,17 @@ void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx
 
         /* Process available complete blocks.  */
         if (len >= 64) {
-                if (UNALIGNED_P(buffer))
+                if (IS_ALIGNED32(buffer)) {
+                        sha256_process_block(buffer, len & ~63, ctx);
+                        buffer = (const char *) buffer + (len & ~63);
+                        len &= 63;
+                } else
                         while (len > 64) {
                                 memcpy(ctx->buffer, buffer, 64);
                                 sha256_process_block(ctx->buffer, 64, ctx);
                                 buffer = (const char *) buffer + 64;
                                 len -= 64;
                         }
-                else {
-                        sha256_process_block(buffer, len & ~63, ctx);
-                        buffer = (const char *) buffer + (len & ~63);
-                        len &= 63;
-                }
         }
 
         /* Move remaining bytes into internal buffer.  */
diff --git a/src/libnm-systemd-shared/src/fundamental/sha256.h b/src/libnm-systemd-shared/src/fundamental/sha256.h
index 31790c2e..dbb08e35 100644
--- a/src/libnm-systemd-shared/src/fundamental/sha256.h
+++ b/src/libnm-systemd-shared/src/fundamental/sha256.h
@@ -1,7 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#include "stdint.h"
+#include <stddef.h>
+#include <stdint.h>
 
 #define SHA256_DIGEST_SIZE 32
 
@@ -28,6 +29,11 @@ void sha256_init_ctx(struct sha256_ctx *ctx);
 uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]);
 void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx);
 
+static inline void sha256_process_bytes_and_size(const void *buffer, size_t len, struct sha256_ctx *ctx) {
+        sha256_process_bytes(&len, sizeof(len), ctx);
+        sha256_process_bytes(buffer, len, ctx);
+}
+
 uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]);
 
 #define SHA256_DIRECT(buffer, sz) sha256_direct(buffer, sz, (uint8_t[SHA256_DIGEST_SIZE]) {})
diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c
index 33f2d860..3a3e7f59 100644
--- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c
+++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.c
@@ -2,7 +2,7 @@
 
 #include "nm-sd-adapt-shared.h"
 
-#ifndef SD_BOOT
+#if !SD_BOOT
 #  include <ctype.h>
 #endif
 
@@ -22,7 +22,6 @@ sd_char *startswith(const sd_char *s, const sd_char *prefix) {
         return (sd_char*) s + l;
 }
 
-#ifndef SD_BOOT
 sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) {
         size_t l;
 
@@ -35,7 +34,6 @@ sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) {
 
         return (sd_char*) s + l;
 }
-#endif
 
 sd_char* endswith(const sd_char *s, const sd_char *postfix) {
         size_t sl, pl;
diff --git a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h
index ecf32e51..9019542b 100644
--- a/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h
+++ b/src/libnm-systemd-shared/src/fundamental/string-util-fundamental.h
@@ -1,9 +1,8 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 #pragma once
 
-#ifdef SD_BOOT
-#  include <efi.h>
-#  include <efilib.h>
+#if SD_BOOT
+#  include "efi.h"
 #  include "efi-string.h"
 #else
 #  include <string.h>
@@ -11,7 +10,7 @@
 
 #include "macro-fundamental.h"
 
-#ifdef SD_BOOT
+#if SD_BOOT
 #  define strlen strlen16
 #  define strcmp strcmp16
 #  define strncmp strncmp16
@@ -59,9 +58,7 @@ static inline size_t strlen_ptr(const sd_char *s) {
 }
 
 sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_;
-#ifndef SD_BOOT
 sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_;
-#endif
 sd_char *endswith(const sd_char *s, const sd_char *postfix) _pure_;
 sd_char *endswith_no_case(const sd_char *s, const sd_char *postfix) _pure_;
 
@@ -110,6 +107,10 @@ static inline bool ascii_isdigit(sd_char a) {
         return a >= '0' && a <= '9';
 }
 
+static inline bool ascii_ishex(sd_char a) {
+        return ascii_isdigit(a) || (a >= 'a' && a <= 'f') || (a >= 'A' && a <= 'F');
+}
+
 static inline bool ascii_isalpha(sd_char a) {
         /* A pure ASCII, locale independent version of isalpha() */
         return (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.c b/src/libnm-systemd-shared/src/shared/dns-domain.c
index 7bba3b18..43f43197 100644
--- a/src/libnm-systemd-shared/src/shared/dns-domain.c
+++ b/src/libnm-systemd-shared/src/shared/dns-domain.c
@@ -302,7 +302,6 @@ int dns_label_escape_new(const char *p, size_t l, char **ret) {
 int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) {
         _cleanup_free_ uint32_t *input = NULL;
         size_t input_size, l;
-        const char *p;
         bool contains_8bit = false;
         char buffer[DNS_LABEL_MAX+1];
         int r;
@@ -319,7 +318,7 @@ int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded
         if (encoded_size <= 0)
                 return -EINVAL;
 
-        for (p = encoded; p < encoded + encoded_size; p++)
+        for (const char *p = encoded; p < encoded + encoded_size; p++)
                 if ((uint8_t) *p > 127)
                         contains_8bit = true;
 
@@ -534,7 +533,18 @@ int dns_name_compare_func(const char *a, const char *b) {
         }
 }
 
-DEFINE_HASH_OPS(dns_name_hash_ops, char, dns_name_hash_func, dns_name_compare_func);
+DEFINE_HASH_OPS(
+        dns_name_hash_ops,
+        char,
+        dns_name_hash_func,
+        dns_name_compare_func);
+
+DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(
+        dns_name_hash_ops_free,
+        char,
+        dns_name_hash_func,
+        dns_name_compare_func,
+        free);
 
 int dns_name_equal(const char *x, const char *y) {
         int r, q;
@@ -752,9 +762,8 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad
                 return r;
         if (r > 0) {
                 uint8_t a[4];
-                unsigned i;
 
-                for (i = 0; i < ELEMENTSOF(a); i++) {
+                for (size_t i = 0; i < ELEMENTSOF(a); i++) {
                         char label[DNS_LABEL_MAX+1];
 
                         r = dns_label_unescape(&p, label, sizeof label, 0);
@@ -788,9 +797,8 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad
                 return r;
         if (r > 0) {
                 struct in6_addr a;
-                unsigned i;
 
-                for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
+                for (size_t i = 0; i < ELEMENTSOF(a.s6_addr); i++) {
                         char label[DNS_LABEL_MAX+1];
                         int x, y;
 
@@ -832,7 +840,6 @@ int dns_name_address(const char *p, int *ret_family, union in_addr_union *ret_ad
 #endif /* NM_IGNORED */
 
 bool dns_name_is_root(const char *name) {
-
         assert(name);
 
         /* There are exactly two ways to encode the root domain name:
@@ -904,8 +911,6 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo
 
 #if 0 /* NM_IGNORED */
 static bool srv_type_label_is_valid(const char *label, size_t n) {
-        size_t k;
-
         assert(label);
 
         if (n < 2) /* Label needs to be at least 2 chars long */
@@ -919,12 +924,11 @@ static bool srv_type_label_is_valid(const char *label, size_t n) {
                 return false;
 
         /* Third and further chars must be alphanumeric or a hyphen */
-        for (k = 2; k < n; k++) {
+        for (size_t k = 2; k < n; k++)
                 if (!ascii_isalpha(label[k]) &&
                     !ascii_isdigit(label[k]) &&
                     label[k] != '-')
                         return false;
-        }
 
         return true;
 }
@@ -1120,14 +1124,12 @@ finish:
 }
 
 static int dns_name_build_suffix_table(const char *name, const char *table[]) {
-        const char *p;
+        const char *p = ASSERT_PTR(name);
         unsigned n = 0;
         int r;
 
-        assert(name);
         assert(table);
 
-        p = name;
         for (;;) {
                 if (n > DNS_N_LABELS_MAX)
                         return -EINVAL;
diff --git a/src/libnm-systemd-shared/src/shared/dns-domain.h b/src/libnm-systemd-shared/src/shared/dns-domain.h
index 828e924d..a9449410 100644
--- a/src/libnm-systemd-shared/src/shared/dns-domain.h
+++ b/src/libnm-systemd-shared/src/shared/dns-domain.h
@@ -62,13 +62,10 @@ static inline int dns_name_is_valid_ldh(const char *s) {
         return 1;
 }
 
-static inline bool dns_name_is_empty(const char *s) {
-        return isempty(s) || streq(s, ".");
-}
-
 void dns_name_hash_func(const char *s, struct siphash *state);
 int dns_name_compare_func(const char *a, const char *b);
 extern const struct hash_ops dns_name_hash_ops;
+extern const struct hash_ops dns_name_hash_ops_free;
 
 int dns_name_between(const char *a, const char *b, const char *c);
 int dns_name_equal(const char *x, const char *y);
diff --git a/src/libnmc-base/nm-polkit-listener.c b/src/libnmc-base/nm-polkit-listener.c
index fbc2c704..c715b049 100644
--- a/src/libnmc-base/nm-polkit-listener.c
+++ b/src/libnmc-base/nm-polkit-listener.c
@@ -499,7 +499,6 @@ out:
 static void
 begin_authentication(AuthRequest *request)
 {
-    int         fd_flags;
     const char *helper_argv[] = {
         POLKIT_AGENT_HELPER_1_PATH,
         request->username,
@@ -514,7 +513,7 @@ begin_authentication(AuthRequest *request)
     if (!g_spawn_async_with_pipes(NULL,
                                   (char **) helper_argv,
                                   NULL,
-                                  G_SPAWN_STDERR_TO_DEV_NULL,
+                                  G_SPAWN_CLOEXEC_PIPES | G_SPAWN_STDERR_TO_DEV_NULL,
                                   NULL,
                                   NULL,
                                   NULL,
@@ -534,11 +533,8 @@ begin_authentication(AuthRequest *request)
         return;
     }
 
-    fd_flags = fcntl(request->child_stdin, F_GETFL, 0);
-    fcntl(request->child_stdin, F_SETFL, fd_flags | O_NONBLOCK);
-
-    fd_flags = fcntl(request->child_stdout, F_GETFL, 0);
-    fcntl(request->child_stdout, F_SETFL, fd_flags | O_NONBLOCK);
+    nm_io_fcntl_setfl_update_nonblock(request->child_stdin);
+    nm_io_fcntl_setfl_update_nonblock(request->child_stdout);
 
     request->child_stdout_watch_source = nm_g_unix_fd_source_new(request->child_stdout,
                                                                  G_IO_IN | G_IO_ERR | G_IO_HUP,
diff --git a/src/libnmc-base/nm-secret-agent-simple.c b/src/libnmc-base/nm-secret-agent-simple.c
index de3de05a..1b9aa571 100644
--- a/src/libnmc-base/nm-secret-agent-simple.c
+++ b/src/libnmc-base/nm-secret-agent-simple.c
@@ -787,7 +787,7 @@ try_spawn_vpn_auth_helper(RequestData *request, GPtrArray *secrets)
     if (!g_spawn_async_with_pipes(NULL,
                                   (char **) auth_dialog_argv->pdata,
                                   NULL,
-                                  G_SPAWN_DO_NOT_REAP_CHILD,
+                                  G_SPAWN_CLOEXEC_PIPES | G_SPAWN_DO_NOT_REAP_CHILD,
                                   NULL,
                                   NULL,
                                   &auth_dialog_pid,
@@ -1084,7 +1084,7 @@ get_secrets(NMSecretAgentOld              *agent,
  * nm_secret_agent_simple_response:
  * @self: the #NMSecretAgentSimple
  * @request_id: the request ID being responded to
- * @secrets: (allow-none): the array of secrets, or %NULL
+ * @secrets: (nullable): the array of secrets, or %NULL
  *
  * Response to a #NMSecretAgentSimple::get-secrets signal.
  *
@@ -1252,7 +1252,7 @@ delete_secrets(NMSecretAgentOld                 *agent,
 /**
  * nm_secret_agent_simple_enable:
  * @self: the #NMSecretAgentSimple
- * @path: (allow-none): the path of the connection (if any) to handle secrets
+ * @path: (nullable): the path of the connection (if any) to handle secrets
  *        for.  If %NULL, secrets for any connection will be handled.
  *
  * Enables servicing the requests including the already queued ones.  If @path
diff --git a/src/libnmc-base/nm-vpn-helpers.c b/src/libnmc-base/nm-vpn-helpers.c
index 476fbe51..cbe76f5f 100644
--- a/src/libnmc-base/nm-vpn-helpers.c
+++ b/src/libnmc-base/nm-vpn-helpers.c
@@ -16,6 +16,7 @@
 #include <net/if.h>
 
 #include "nm-client-utils.h"
+#include "nm-secret-agent-simple.h"
 #include "nm-utils.h"
 #include "libnm-glib-aux/nm-io-utils.h"
 #include "libnm-glib-aux/nm-secret-utils.h"
@@ -160,9 +161,10 @@ nm_vpn_get_secret_names(const char *service_type)
     };
 
     if (NM_IN_STRSET(type, "openconnect")) {
-        return _VPN_PASSWORD_LIST({"gateway", N_("Gateway")},
+        return _VPN_PASSWORD_LIST({"gateway", N_("Gateway URL")},
                                   {"cookie", N_("Cookie")},
-                                  {"gwcert", N_("Gateway certificate hash")}, );
+                                  {"gwcert", N_("Gateway certificate hash")},
+                                  {"resolve", N_("Gateway DNS resolution ('host:IP')")}, );
     };
 
     return NULL;
@@ -186,18 +188,102 @@ _extract_variable_value(char *line, const char *tag, char **value)
     return TRUE;
 }
 
+#define NM_OPENCONNECT_KEY_GATEWAY              "gateway"
+#define NM_OPENCONNECT_KEY_COOKIE               "cookie"
+#define NM_OPENCONNECT_KEY_GWCERT               "gwcert"
+#define NM_OPENCONNECT_KEY_RESOLVE              "resolve"
+#define NM_OPENCONNECT_KEY_AUTHTYPE             "authtype"
+#define NM_OPENCONNECT_KEY_USERCERT             "usercert"
+#define NM_OPENCONNECT_KEY_CACERT               "cacert"
+#define NM_OPENCONNECT_KEY_PRIVKEY              "userkey"
+#define NM_OPENCONNECT_KEY_KEY_PASS             "key_pass"
+#define NM_OPENCONNECT_KEY_MTU                  "mtu"
+#define NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID  "pem_passphrase_fsid"
+#define NM_OPENCONNECT_KEY_PREVENT_INVALID_CERT "prevent_invalid_cert"
+#define NM_OPENCONNECT_KEY_DISABLE_UDP          "disable_udp"
+#define NM_OPENCONNECT_KEY_PROTOCOL             "protocol"
+#define NM_OPENCONNECT_KEY_PROXY                "proxy"
+#define NM_OPENCONNECT_KEY_CSD_ENABLE           "enable_csd_trojan"
+#define NM_OPENCONNECT_KEY_USERAGENT            "useragent"
+#define NM_OPENCONNECT_KEY_CSD_WRAPPER          "csd_wrapper"
+#define NM_OPENCONNECT_KEY_TOKEN_MODE           "stoken_source"
+#define NM_OPENCONNECT_KEY_TOKEN_SECRET         "stoken_string"
+#define NM_OPENCONNECT_KEY_REPORTED_OS          "reported_os"
+#define NM_OPENCONNECT_KEY_MCACERT              "mcacert"
+#define NM_OPENCONNECT_KEY_MCAKEY               "mcakey"
+#define NM_OPENCONNECT_KEY_MCA_PASS             "mca_key_pass"
+
+static const struct {
+    const char *property;
+    const char *cmdline;
+} oc_property_args[] = {
+    {NM_OPENCONNECT_KEY_USERCERT, "--certificate"},
+    {NM_OPENCONNECT_KEY_CACERT, "--cafile"},
+    {NM_OPENCONNECT_KEY_PRIVKEY, "--sslkey"},
+    {NM_OPENCONNECT_KEY_KEY_PASS, "--key-password"},
+    {NM_OPENCONNECT_KEY_PROTOCOL, "--protocol"},
+    {NM_OPENCONNECT_KEY_PROXY, "--proxy"},
+    {NM_OPENCONNECT_KEY_USERAGENT, "--useragent"},
+    {NM_OPENCONNECT_KEY_REPORTED_OS, "--os"},
+    {NM_OPENCONNECT_KEY_MCACERT, "--mca-certificate"},
+    {NM_OPENCONNECT_KEY_MCAKEY, "--mca-key"},
+    {NM_OPENCONNECT_KEY_MCA_PASS, "--mca-key-password"},
+};
+
+/*
+ * For old versions of openconnect we need to extract the port# and
+ * append it to the hostname that is returned to us. Use a cut-down
+ * version of openconnect's own internal_parse_url() function.
+ */
+static int
+extract_url_port(const char *url)
+{
+    const char *host, *port_str, *path;
+    char       *end;
+    int         port_nr;
+
+    /* Skip the scheme, if present */
+    host = strstr(url, "://");
+    if (host)
+        host += 3;
+    else
+        host = url;
+
+    port_str = strrchr(host, ':');
+    if (!port_str)
+        return 0;
+
+    /*
+     * If the host is an IPv6 literal, port_str may point somewhere
+     * inside it rather than to an actual port#. But IPv6 literals
+     * are always enclosed in [], e.g. '[fec0::1]:443'. So we check
+     * that the end pointer returned by strtol points exactly to the
+     * end of the hostname (either the end of the string, or to the
+     * first '/' of the path element if there is one).
+     */
+    path    = strchr(host, '/');
+    port_nr = strtol(port_str + 1, &end, 10);
+
+    if (end == path || (!path && !*end))
+        return port_nr;
+
+    return 0;
+}
+
 gboolean
-nm_vpn_openconnect_authenticate_helper(const char *host,
-                                       char      **cookie,
-                                       char      **gateway,
-                                       char      **gwcert,
-                                       int        *status,
-                                       GError    **error)
+nm_vpn_openconnect_authenticate_helper(NMSettingVpn *s_vpn, GPtrArray *secrets, GError **error)
 {
-    gs_free char        *output   = NULL;
-    gs_free const char **output_v = NULL;
+    gs_free char        *output      = NULL;
+    gs_free char        *legacy_host = NULL;
+    gs_free char        *connect_url = NULL;
+    gs_free char        *cookie      = NULL;
+    gs_free char        *gwcert      = NULL;
+    gs_free char        *resolve     = NULL;
+    gs_free const char **output_v    = NULL;
+    int                  status      = 0;
     const char *const   *iter;
     const char          *path;
+    const char          *opt;
     const char *const    DEFAULT_PATHS[] = {
         "/sbin/",
         "/usr/sbin/",
@@ -207,6 +293,23 @@ nm_vpn_openconnect_authenticate_helper(const char *host,
         "/usr/local/bin/",
         NULL,
     };
+    const char *oc_argv[(12 + 2 * G_N_ELEMENTS(oc_property_args))];
+    const char *gw;
+    int         port;
+    guint       oc_argc = 0;
+    guint       i;
+
+    /* Get gateway and port */
+    gw = nm_setting_vpn_get_data_item(s_vpn, "gateway");
+    if (!gw) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("no gateway configured"));
+        return FALSE;
+    }
+
+    port = extract_url_port(gw);
 
     path = nm_utils_file_search_in_paths("openconnect",
                                          "/usr/sbin/openconnect",
@@ -218,31 +321,144 @@ nm_vpn_openconnect_authenticate_helper(const char *host,
     if (!path)
         return FALSE;
 
+    oc_argv[oc_argc++] = path;
+    oc_argv[oc_argc++] = "--authenticate";
+    oc_argv[oc_argc++] = gw;
+
+    for (i = 0; i < G_N_ELEMENTS(oc_property_args); i++) {
+        opt = nm_setting_vpn_get_data_item(s_vpn, oc_property_args[i].property);
+        if (opt) {
+            oc_argv[oc_argc++] = oc_property_args[i].cmdline;
+            oc_argv[oc_argc++] = opt;
+        }
+    }
+
+    opt = nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_PEM_PASSPHRASE_FSID);
+    if (opt && nm_streq(opt, "yes"))
+        oc_argv[oc_argc++] = "--key-password-from-fsid";
+
+    opt = nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_CSD_ENABLE);
+    if (opt && nm_streq(opt, "yes")) {
+        opt = nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_CSD_WRAPPER);
+        if (opt) {
+            oc_argv[oc_argc++] = "--csd-wrapper";
+            oc_argv[oc_argc++] = opt;
+        }
+    }
+
+    opt = nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_TOKEN_MODE);
+    if (opt) {
+        const char *token_secret =
+            nm_setting_vpn_get_data_item(s_vpn, NM_OPENCONNECT_KEY_TOKEN_SECRET);
+        if (nm_streq(opt, "manual") && token_secret) {
+            opt = "rsa";
+        } else if (nm_streq(opt, "stokenrc")) {
+            opt          = "rsa";
+            token_secret = NULL;
+        } else if (!nm_streq(opt, "totp") && !nm_streq(opt, "hotp") && !nm_streq(opt, "yubioath")) {
+            opt = NULL;
+        }
+        if (opt) {
+            oc_argv[oc_argc++] = "--token-mode";
+            oc_argv[oc_argc++] = opt;
+        }
+        if (token_secret) {
+            oc_argv[oc_argc++] = "--token-secret";
+            oc_argv[oc_argc++] = token_secret;
+        }
+    }
+
+    oc_argv[oc_argc++] = NULL;
+
+    nm_assert(oc_argc <= G_N_ELEMENTS(oc_argv));
+
     if (!g_spawn_sync(NULL,
-                      (char **) NM_MAKE_STRV(path, "--authenticate", host),
+                      (char **) oc_argv,
                       NULL,
                       G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN,
                       NULL,
                       NULL,
                       &output,
                       NULL,
-                      status,
+                      &status,
                       error))
         return FALSE;
 
+    if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
+        /* The caller will prepend "Error: openconnect failed: " to this */
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("exited with status %d"),
+                    WEXITSTATUS(status));
+        return FALSE;
+    } else if (WIFSIGNALED(status)) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("exited on signal %d"),
+                    WTERMSIG(status));
+        return FALSE;
+    }
+
     /* Parse output and set cookie, gateway and gwcert
      * output example:
      * COOKIE='loremipsum'
      * HOST='1.2.3.4'
      * FINGERPRINT='sha1:32bac90cf09a722e10ecc1942c67fe2ac8c21e2e'
+     *
+     * Since OpenConnect v8.20 (2022-02-20) OpenConnect has also passed e.g.:
+     *
+     * CONNECT_URL='https://vpn.example.com:8443/ConnectPath'
+     * RESOLVE=vpn.example.com:1.2.3.4
      */
     output_v = nm_strsplit_set_with_empty(output, "\r\n");
     for (iter = output_v; iter && *iter; iter++) {
         char *s_mutable = (char *) *iter;
 
-        _extract_variable_value(s_mutable, "COOKIE=", cookie);
-        _extract_variable_value(s_mutable, "HOST=", gateway);
-        _extract_variable_value(s_mutable, "FINGERPRINT=", gwcert);
+        _extract_variable_value(s_mutable, "COOKIE=", &cookie);
+        _extract_variable_value(s_mutable, "CONNECT_URL=", &connect_url);
+        _extract_variable_value(s_mutable, "HOST=", &legacy_host);
+        _extract_variable_value(s_mutable, "FINGERPRINT=", &gwcert);
+        _extract_variable_value(s_mutable, "RESOLVE=", &resolve);
+    }
+
+    if (!cookie || !gwcert || (!legacy_host && !connect_url)) {
+        g_set_error(error,
+                    NM_VPN_PLUGIN_ERROR,
+                    NM_VPN_PLUGIN_ERROR_FAILED,
+                    _("insufficent secrets returned"));
+        return FALSE;
+    }
+
+    for (i = 0; i < secrets->len; i++) {
+        NMSecretAgentSimpleSecret *secret = secrets->pdata[i];
+
+        if (secret->secret_type != NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET)
+            continue;
+        if (!nm_streq0(secret->vpn_type, NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT))
+            continue;
+        if (nm_streq0(secret->entry_id, NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "cookie")) {
+            g_free(secret->value);
+            secret->value = g_steal_pointer(&cookie);
+        } else if (nm_streq0(secret->entry_id,
+                             NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gateway")) {
+            g_free(secret->value);
+            if (connect_url)
+                secret->value = g_steal_pointer(&connect_url);
+            else if (port)
+                secret->value = g_strdup_printf("%s:%d", legacy_host, port);
+            else
+                secret->value = g_steal_pointer(&legacy_host);
+        } else if (nm_streq0(secret->entry_id,
+                             NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gwcert")) {
+            g_free(secret->value);
+            secret->value = g_steal_pointer(&gwcert);
+        } else if (nm_streq0(secret->entry_id,
+                             NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "resolve")) {
+            g_free(secret->value);
+            secret->value = g_steal_pointer(&resolve);
+        }
     }
 
     return TRUE;
diff --git a/src/libnmc-base/nm-vpn-helpers.h b/src/libnmc-base/nm-vpn-helpers.h
index 8bea8460..afd56590 100644
--- a/src/libnmc-base/nm-vpn-helpers.h
+++ b/src/libnmc-base/nm-vpn-helpers.h
@@ -19,11 +19,7 @@ gboolean nm_vpn_supports_ipv6(NMConnection *connection);
 
 const NmcVpnPasswordName *nm_vpn_get_secret_names(const char *service_type);
 
-gboolean nm_vpn_openconnect_authenticate_helper(const char *host,
-                                                char      **cookie,
-                                                char      **gateway,
-                                                char      **gwcert,
-                                                int        *status,
-                                                GError    **error);
+gboolean
+nm_vpn_openconnect_authenticate_helper(NMSettingVpn *s_vpn, GPtrArray *secrets, GError **error);
 
 #endif /* __NM_VPN_HELPERS_H__ */
diff --git a/src/libnmc-setting/nm-meta-setting-base-impl.c b/src/libnmc-setting/nm-meta-setting-base-impl.c
index 19082671..b531ae85 100644
--- a/src/libnmc-setting/nm-meta-setting-base-impl.c
+++ b/src/libnmc-setting/nm-meta-setting-base-impl.c
@@ -34,6 +34,7 @@
 #include "nm-setting-ip-tunnel.h"
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-ip6-config.h"
+#include "nm-setting-link.h"
 #include "nm-setting-loopback.h"
 #include "nm-setting-macsec.h"
 #include "nm-setting-macvlan.h"
@@ -362,6 +363,13 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
             .setting_name      = NM_SETTING_IP_TUNNEL_SETTING_NAME,
             .get_setting_gtype = nm_setting_ip_tunnel_get_type,
         },
+    [NM_META_SETTING_TYPE_LINK] =
+        {
+            .meta_type         = NM_META_SETTING_TYPE_LINK,
+            .setting_priority  = NM_SETTING_PRIORITY_AUX,
+            .setting_name      = NM_SETTING_LINK_SETTING_NAME,
+            .get_setting_gtype = nm_setting_link_get_type,
+        },
     [NM_META_SETTING_TYPE_LOOPBACK] =
         {
             .meta_type         = NM_META_SETTING_TYPE_LOOPBACK,
@@ -660,6 +668,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = {
     NM_META_SETTING_TYPE_BOND_PORT,
     NM_META_SETTING_TYPE_BRIDGE_PORT,
     NM_META_SETTING_TYPE_ETHTOOL,
+    NM_META_SETTING_TYPE_LINK,
     NM_META_SETTING_TYPE_MATCH,
     NM_META_SETTING_TYPE_OVS_EXTERNAL_IDS,
     NM_META_SETTING_TYPE_OVS_OTHER_CONFIG,
diff --git a/src/libnmc-setting/nm-meta-setting-base-impl.h b/src/libnmc-setting/nm-meta-setting-base-impl.h
index 0c2def90..c6d1c2fc 100644
--- a/src/libnmc-setting/nm-meta-setting-base-impl.h
+++ b/src/libnmc-setting/nm-meta-setting-base-impl.h
@@ -127,6 +127,7 @@ typedef enum _nm_packed {
     NM_META_SETTING_TYPE_IP_TUNNEL,
     NM_META_SETTING_TYPE_IP4_CONFIG,
     NM_META_SETTING_TYPE_IP6_CONFIG,
+    NM_META_SETTING_TYPE_LINK,
     NM_META_SETTING_TYPE_LOOPBACK,
     NM_META_SETTING_TYPE_MACSEC,
     NM_META_SETTING_TYPE_MACVLAN,
diff --git a/src/libnmc-setting/nm-meta-setting-desc.c b/src/libnmc-setting/nm-meta-setting-desc.c
index e9185e34..368beaab 100644
--- a/src/libnmc-setting/nm-meta-setting-desc.c
+++ b/src/libnmc-setting/nm-meta-setting-desc.c
@@ -2474,7 +2474,9 @@ _get_fcn_bond_options(ARGS_GET_FCN)
 
         nm_setting_bond_get_option(s_bond, i, &key, &val);
 
-        if (nm_streq(key, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)) {
+        if (NM_IN_STRSET(key,
+                         NM_SETTING_BOND_OPTION_ARP_IP_TARGET,
+                         NM_SETTING_BOND_OPTION_NS_IP6_TARGET)) {
             val_tmp = g_strdup(val);
             for (p = val_tmp; p && *p; p++) {
                 if (*p == ',')
@@ -2516,7 +2518,8 @@ _nm_meta_setting_bond_add_option(NMSetting  *setting,
         value = nmc_bond_validate_mode(value, error);
         if (!value)
             return FALSE;
-    } else if (nm_streq(name, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)) {
+    } else if (nm_streq(name, NM_SETTING_BOND_OPTION_ARP_IP_TARGET)
+               || nm_streq(name, NM_SETTING_BOND_OPTION_NS_IP6_TARGET)) {
         value = tmp_value = g_strdup(value);
         for (p = tmp_value; p && *p; p++)
             if (*p == ' ')
@@ -3097,7 +3100,7 @@ dcb_parse_uint_array(const char *val,
 
 static void
 dcb_check_feature_enabled(const NMMetaEnvironment *environment,
-                          gpointer                *environment_user_data,
+                          gpointer                 environment_user_data,
                           NMSettingDcb            *s_dcb,
                           const char              *flags_prop)
 {
@@ -5534,9 +5537,6 @@ static const NMMetaPropertyInfo *const property_infos_CONNECTION[] = {
     PROPERTY_INFO_WITH_DESC (NM_SETTING_CONNECTION_TIMESTAMP,
         .property_type =                &_pt_gobject_readonly,
     ),
-    PROPERTY_INFO_WITH_DESC (NM_SETTING_CONNECTION_READ_ONLY,
-        .property_type =                &_pt_gobject_readonly,
-    ),
     PROPERTY_INFO_WITH_DESC (NM_SETTING_CONNECTION_PERMISSIONS,
         .describe_message =
              N_("Enter a list of user permissions. This is a list of user names formatted as:\n"
@@ -5606,7 +5606,7 @@ static const NMMetaPropertyInfo *const property_infos_CONNECTION[] = {
                 .remove_by_idx_fcn_u32 = MULTILIST_REMOVE_BY_IDX_FCN_U32 (NMSettingConnection, nm_setting_connection_remove_secondary),
                 .remove_by_value_fcn =  MULTILIST_REMOVE_BY_VALUE_FCN (NMSettingConnection, nm_setting_connection_remove_secondary_by_value),
                 .validate2_fcn =        _multilist_validate2_fcn_uuid,
-                .strsplit_plain =       TRUE,
+                .strsplit_with_spaces = TRUE,
             ),
         ),
     ),
@@ -5977,6 +5977,12 @@ static const NMMetaPropertyInfo *const property_infos_GSM[] = {
             .get_fcn =                  MTU_GET_FCN (NMSettingGsm, nm_setting_gsm_get_mtu),
         ),
     ),
+    PROPERTY_INFO_WITH_DESC (NM_SETTING_GSM_INITIAL_EPS_BEARER_CONFIGURE,
+        .property_type =                &_pt_gobject_bool,
+    ),
+    PROPERTY_INFO_WITH_DESC (NM_SETTING_GSM_INITIAL_EPS_BEARER_APN,
+        .property_type =                &_pt_gobject_string,
+    ),
     NULL
 };
 
@@ -6521,6 +6527,9 @@ static const NMMetaPropertyInfo *const property_infos_IP6_CONFIG[] = {
             .get_fcn =                  MTU_GET_FCN (NMSettingIP6Config, nm_setting_ip6_config_get_mtu),
         ),
     ),
+    PROPERTY_INFO (NM_SETTING_IP6_CONFIG_DHCP_PD_HINT, DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_PD_HINT,
+        .property_type =                &_pt_gobject_string,
+    ),
     PROPERTY_INFO (NM_SETTING_IP6_CONFIG_DHCP_DUID, DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_DUID,
         .property_type =                &_pt_gobject_string,
     ),
@@ -6649,6 +6658,56 @@ static const NMMetaPropertyInfo *const property_infos_LOOPBACK[] = {
 };
 
 #undef  _CURRENT_NM_META_SETTING_TYPE
+#define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_LINK
+static const NMMetaPropertyInfo *const property_infos_LINK[] = {
+    PROPERTY_INFO_WITH_DESC (NM_SETTING_LINK_GSO_MAX_SEGMENTS,
+        .property_type =                &_pt_gobject_int,
+        .property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (gobject_int,
+            .value_infos =              INT_VALUE_INFOS (
+                {
+                    .value.i64 = -1,
+                    .nick = "default",
+                },
+            ),
+        ),
+    ),
+    PROPERTY_INFO_WITH_DESC (NM_SETTING_LINK_GSO_MAX_SIZE,
+        .property_type =                &_pt_gobject_int,
+        .property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (gobject_int,
+            .value_infos =              INT_VALUE_INFOS (
+                {
+                    .value.i64 = -1,
+                    .nick = "default",
+                },
+            ),
+        ),
+    ),
+    PROPERTY_INFO_WITH_DESC (NM_SETTING_LINK_GRO_MAX_SIZE,
+        .property_type =                &_pt_gobject_int,
+        .property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (gobject_int,
+            .value_infos =              INT_VALUE_INFOS (
+                {
+                    .value.i64 = -1,
+                    .nick = "default",
+                },
+            ),
+        ),
+    ),
+    PROPERTY_INFO_WITH_DESC (NM_SETTING_LINK_TX_QUEUE_LENGTH,
+        .property_type =                &_pt_gobject_int,
+        .property_typ_data = DEFINE_PROPERTY_TYP_DATA_SUBTYPE (gobject_int,
+            .value_infos =              INT_VALUE_INFOS (
+                {
+                    .value.i64 = -1,
+                    .nick = "default",
+                },
+            ),
+        ),
+    ),
+    NULL
+};
+
+#undef  _CURRENT_NM_META_SETTING_TYPE
 #define _CURRENT_NM_META_SETTING_TYPE NM_META_SETTING_TYPE_MACSEC
 static const NMMetaPropertyInfo *const property_infos_MACSEC[] = {
     PROPERTY_INFO_WITH_DESC (NM_SETTING_MACSEC_PARENT,
@@ -7974,14 +8033,6 @@ static const NMMetaPropertyInfo *const property_infos_WIRELESS[] = {
     PROPERTY_INFO_WITH_DESC (NM_SETTING_WIRELESS_BSSID,
         .property_type =                &_pt_gobject_mac,
     ),
-    PROPERTY_INFO_WITH_DESC (NM_SETTING_WIRELESS_RATE,
-        /* Do not allow setting 'rate'. It is not implemented in NM core. */
-        .property_type =                &_pt_gobject_readonly,
-    ),
-    PROPERTY_INFO_WITH_DESC (NM_SETTING_WIRELESS_TX_POWER,
-        /* Do not allow setting 'tx-power'. It is not implemented in NM core. */
-        .property_type =                &_pt_gobject_readonly,
-    ),
     PROPERTY_INFO_WITH_DESC (NM_SETTING_WIRELESS_MAC_ADDRESS,
         .property_type =                &_pt_gobject_mac,
         .is_cli_option =                TRUE,
@@ -8427,6 +8478,7 @@ _setting_init_fcn_wireless (ARGS_SETTING_INIT_FCN)
 #define SETTING_PRETTY_NAME_IP4_CONFIG          N_("IPv4 protocol")
 #define SETTING_PRETTY_NAME_IP6_CONFIG          N_("IPv6 protocol")
 #define SETTING_PRETTY_NAME_IP_TUNNEL           N_("IP-tunnel settings")
+#define SETTING_PRETTY_NAME_LINK                N_("Link settings")
 #define SETTING_PRETTY_NAME_LOOPBACK            N_("Loopback settings")
 #define SETTING_PRETTY_NAME_MACSEC              N_("MACsec connection")
 #define SETTING_PRETTY_NAME_MACVLAN             N_("macvlan connection")
@@ -8588,6 +8640,7 @@ const NMMetaSettingInfoEditor nm_meta_setting_infos_editor[] = {
             NM_META_SETTING_VALID_PART_ITEM (ETHTOOL,               FALSE),
         ),
     ),
+    SETTING_INFO (LINK),
     SETTING_INFO (LOOPBACK,
         .valid_parts = NM_META_SETTING_VALID_PARTS (
             NM_META_SETTING_VALID_PART_ITEM (CONNECTION,            TRUE),
@@ -8802,6 +8855,7 @@ static const NMMetaSettingValidPartItem *const valid_settings_noslave[] = {
     NM_META_SETTING_VALID_PART_ITEM(IP4_CONFIG, FALSE),
     NM_META_SETTING_VALID_PART_ITEM(IP6_CONFIG, FALSE),
     NM_META_SETTING_VALID_PART_ITEM(HOSTNAME, FALSE),
+    NM_META_SETTING_VALID_PART_ITEM(LINK, FALSE),
     NM_META_SETTING_VALID_PART_ITEM(TC_CONFIG, FALSE),
     NM_META_SETTING_VALID_PART_ITEM(PROXY, FALSE),
     NULL,
@@ -8809,11 +8863,15 @@ static const NMMetaSettingValidPartItem *const valid_settings_noslave[] = {
 
 static const NMMetaSettingValidPartItem *const valid_settings_slave_bond[] = {
     NM_META_SETTING_VALID_PART_ITEM(BOND_PORT, TRUE),
+    NM_META_SETTING_VALID_PART_ITEM(LINK, FALSE),
+    NM_META_SETTING_VALID_PART_ITEM(MATCH, FALSE),
     NULL,
 };
 
 static const NMMetaSettingValidPartItem *const valid_settings_slave_bridge[] = {
     NM_META_SETTING_VALID_PART_ITEM(BRIDGE_PORT, TRUE),
+    NM_META_SETTING_VALID_PART_ITEM(LINK, FALSE),
+    NM_META_SETTING_VALID_PART_ITEM(MATCH, FALSE),
     NULL,
 };
 
@@ -8823,11 +8881,15 @@ static const NMMetaSettingValidPartItem *const valid_settings_slave_ovs_bridge[]
 };
 
 static const NMMetaSettingValidPartItem *const valid_settings_slave_ovs_port[] = {
+    NM_META_SETTING_VALID_PART_ITEM(LINK, FALSE),
+    NM_META_SETTING_VALID_PART_ITEM(MATCH, FALSE),
     NM_META_SETTING_VALID_PART_ITEM(OVS_INTERFACE, FALSE),
     NULL,
 };
 
 static const NMMetaSettingValidPartItem *const valid_settings_slave_team[] = {
+    NM_META_SETTING_VALID_PART_ITEM(LINK, FALSE),
+    NM_META_SETTING_VALID_PART_ITEM(MATCH, FALSE),
     NM_META_SETTING_VALID_PART_ITEM(TEAM_PORT, TRUE),
     NULL,
 };
diff --git a/src/libnmc-setting/settings-docs.h b/src/libnmc-setting/settings-docs.h
index 0b2f8eca..f06c981a 100644
--- a/src/libnmc-setting/settings-docs.h
+++ b/src/libnmc-setting/settings-docs.h
@@ -18,10 +18,10 @@
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_MUD_URL N_("If configured, set to a Manufacturer Usage Description (MUD) URL that points to manufacturer-recommended network policies for IoT devices. It is transmitted as a DHCPv4 or DHCPv6 option. The value must be a valid URL starting with \"https://\". The special value \"none\" is allowed to indicate that no MUD URL is used. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the ultimate default is \"none\".")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_MULTI_CONNECT N_("Specifies whether the profile can be active multiple times at a particular moment. The value is of type NMConnectionMultiConnect.")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_PERMISSIONS N_("An array of strings defining what access a given user has to this connection.  If this is NULL or empty, all users are allowed to access this connection; otherwise users are allowed if and only if they are in this list.  When this is not empty, the connection can be active only when one of the specified users is logged into an active session.  Each entry is of the form \"[type]:[id]:[reserved]\"; for example, \"user:dcbw:blah\". At this time only the \"user\" [type] is allowed.  Any other values are ignored and reserved for future use.  [id] is the username that this permission refers to, which may not contain the \":\" character. Any [reserved] information present must be ignored and is reserved for future use.  All of [type], [id], and [reserved] must be valid UTF-8.")
-#define DESCRIBE_DOC_NM_SETTING_CONNECTION_READ_ONLY N_("FALSE if the connection can be modified using the provided settings service's D-Bus interface with the right privileges, or TRUE if the connection is read-only and cannot be modified.")
+#define DESCRIBE_DOC_NM_SETTING_CONNECTION_READ_ONLY N_("This property is deprecated and has no meaning.")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_SECONDARIES N_("List of connection UUIDs that should be activated when the base connection itself is activated. Currently, only VPN connections are supported.")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_SLAVE_TYPE N_("Setting name of the device type of this slave's master connection (eg, \"bond\"), or NULL if this connection is not a slave.")
-#define DESCRIBE_DOC_NM_SETTING_CONNECTION_STABLE_ID N_("This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable and to derive the DHCP DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid]. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device's name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key. See NetworkManager(8) manual about the secret-key and the host identity. The '$' character is treated special to perform dynamic substitutions at runtime. Currently, supported are \"${CONNECTION}\", \"${DEVICE}\", \"${MAC}\", \"${BOOT}\", \"${RANDOM}\". These effectively create unique IDs per-connection, per-device, per-boot, or every time. Note that \"${DEVICE}\" corresponds to the interface name of the device and \"${MAC}\" is the permanent MAC address of the device. Any unrecognized patterns following '$' are treated verbatim, however are reserved for future use. You are thus advised to avoid '$' or escape it as \"$$\". For example, set it to \"${CONNECTION}-${BOOT}-${DEVICE}\" to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is similar to \"${CONNECTION}\" and uses a unique, fixed ID for the connection.")
+#define DESCRIBE_DOC_NM_SETTING_CONNECTION_STABLE_ID N_("This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used to derive the DHCP client identifier with ipv4.dhcp-client-id=stable, the DHCPv6 DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid] and the DHCP IAID with ipv4.iaid=stable and ipv6.iaid=stable. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device's name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key. See NetworkManager(8) manual about the secret-key and the host identity. The '$' character is treated special to perform dynamic substitutions at activation time. Currently, supported are \"${CONNECTION}\", \"${DEVICE}\", \"${MAC}\", \"${BOOT}\", \"${RANDOM}\".  These effectively create unique IDs per-connection, per-device, per-boot, or every time. The \"${CONNECTION}\" uses the profile's connection.uuid, the \"${DEVICE}\" uses the interface name of the device and \"${MAC}\" the permanent MAC address of the device. Any unrecognized patterns following '$' are treated verbatim, however are reserved for future use. You are thus advised to avoid '$' or escape it as \"$$\".  For example, set it to \"${CONNECTION}-${BOOT}-${DEVICE}\" to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is \"default${CONNECTION}\" go generate an ID unique per connection profile.")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_TIMESTAMP N_("The time, in seconds since the Unix Epoch, that the connection was last _successfully_ fully activated. NetworkManager updates the connection timestamp periodically when the connection is active to ensure that an active connection has the latest timestamp. The property is only meant for reading (changes to this property will not be preserved).")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_TYPE N_("Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, \"802-3-ethernet\" or \"802-11-wireless\" or \"bluetooth\", etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, \"vpn\" or \"bridge\", etc).")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_UUID N_("The connection.uuid is the real identifier of a profile. It cannot change and it must be unique. It is therefore often best to refer to a profile by UUID, for example with `nmcli connection up uuid $UUID`. The UUID cannot be changed, except in offline mode. In that case, the special values \"new\", \"generate\" and \"\" are allowed to generate a new random UUID.")
@@ -141,6 +141,8 @@
 #define DESCRIBE_DOC_NM_SETTING_GSM_AUTO_CONFIG N_("When TRUE, the settings such as APN, username, or password will default to values that match the network the modem will register to in the Mobile Broadband Provider database.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_DEVICE_ID N_("The device unique identifier (as given by the WWAN management service) which this connection applies to.  If given, the connection will only apply to the specified device.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_HOME_ONLY N_("When TRUE, only connections to the home network will be allowed. Connections to roaming networks will not be made.")
+#define DESCRIBE_DOC_NM_SETTING_GSM_INITIAL_EPS_BEARER_APN N_("For LTE modems, this sets the APN for the initial EPS bearer that is set up when attaching to the network.  Setting this parameter implies initial-eps-bearer-configure to be TRUE.")
+#define DESCRIBE_DOC_NM_SETTING_GSM_INITIAL_EPS_BEARER_CONFIGURE N_("For LTE modems, this setting determines whether the initial EPS bearer shall be configured when bringing up the connection.  It is inferred TRUE if initial-eps-bearer-apn is set.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_NETWORK_ID N_("The Network ID (GSM LAI format, ie MCC-MNC) to force specific network registration.  If the Network ID is specified, NetworkManager will attempt to force the device to register only on the specified network. This can be used to ensure that the device does not roam when direct roaming control of the device is not otherwise possible.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_NUMBER N_("Legacy setting that used to help establishing PPP data sessions for GSM-based modems.")
@@ -163,13 +165,13 @@
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_FQDN N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified FQDN will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-hostname\" are mutually exclusive and cannot be set at the same time.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_HOSTNAME N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-fqdn\" are mutually exclusive and cannot be set at the same time.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_HOSTNAME_FLAGS N_("Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) and NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE (0x4).  When no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is not set, the standard FQDN flags are set in the request: NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) for IPv4 and NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1) for IPv6. When this property is set to the default value NM_DHCP_HOSTNAME_FLAG_NONE (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also NM_DHCP_HOSTNAME_FLAG_NONE (0x0), then the standard FQDN flags described above are sent in the DHCP requests.")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_REJECT_SERVERS N_("Array of servers from which DHCP offers must be rejected. This property is useful to avoid getting a lease from misconfigured or rogue servers. For DHCPv4, each element must be an IPv4 address, optionally followed by a slash and a prefix length (e.g. \"192.168.122.0/24\"). This property is currently not implemented for DHCPv6.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER N_("The Vendor Class Identifier DHCP option (60). Special characters in the data string may be escaped using C-style escapes, nevertheless this property cannot contain nul bytes. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the DHCP option is not sent to the server.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server. This currently only has effect when using systemd-resolved.")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("DNS options for /etc/resolv.conf as described in resolv.conf(5) manual. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". See the resolv.conf(5) manual. Note that there is a distinction between an unset (default) list and an empty list. In nmcli, to unset the list set the value to \"\". To set an empty list, set it to \" \". Currently, an unset list has the same meaning as an empty list. That might change in the future. The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added. The valid \"ipv4.dns-options\" and \"ipv6.dns-options\" get merged together.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".")
@@ -177,7 +179,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_IGNORE_AUTO_ROUTES N_("When \"method\" is set to \"auto\" and this property to TRUE, automatically configured routes are ignored and only routes specified in the \"routes\" property, if any, are used.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_LINK_LOCAL N_("Enable and disable the IPv4 link-local configuration independently of the ipv4.method configuration. This allows a link-local address (169.254.x.y/16) to be obtained in addition to other addresses, such as those manually configured or obtained from a DHCP server. When set to \"auto\", the value is dependent on \"ipv4.method\". When set to \"default\", it honors the global connection default, before falling back to \"auto\". Note that if \"ipv4.method\" is \"disabled\", then link local addressing is always disabled too. The default is \"default\".")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_MAY_FAIL N_("If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out.  Note that at least one IP configuration must succeed or overall network configuration will still fail.  For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully.")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_METHOD N_("IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support \"disabled\", \"auto\", \"manual\", and \"link-local\". See the subclass-specific documentation for other values. In general, for the \"auto\" method, properties such as \"dns\" and \"routes\" specify information that is added on to the information returned from automatic configuration.  The \"ignore-auto-routes\" and \"ignore-auto-dns\" properties modify this behavior. For methods that imply no upstream network, such as \"shared\" or \"link-local\", these properties must be empty. For IPv4 method \"shared\", the IP subnet can be configured by adding one manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the shared method must be configured on the interface which shares the internet to a subnet, not on the uplink which is shared.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_METHOD N_("The IPv4 connection method.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_NEVER_DEFAULT N_("If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_REPLACE_LOCAL_RULE N_("Connections will default to keep the autogenerated priority 0 local rule unless this setting is set to TRUE.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_REQUIRED_TIMEOUT N_("The minimum time interval in milliseconds for which dynamic IP configuration should be tried before the connection succeeds. This property is useful for example if both IPv4 and IPv6 are enabled and are allowed to fail. Normally the connection succeeds as soon as one of the two address families completes; by setting a required timeout for e.g. IPv4, one can ensure that even if IP6 succeeds earlier than IPv4, NetworkManager waits some time for IPv4 before the connection becomes active. Note that if \"may-fail\" is FALSE for the same address family, this property has no effect as NetworkManager needs to wait for the full DHCP timeout. A zero value means that no required timeout is present, -1 means the default value (either configuration ipvx.required-timeout override or zero).")
@@ -185,19 +187,20 @@
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_ROUTE_TABLE N_("Enable policy routing (source routing) and set the routing table used when adding routes. This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes and static routes. But note that static routes can individually overwrite the setting by explicitly specifying a non-zero routing table. If the table setting is left at zero, it is eligible to be overwritten via global configuration. If the property is zero even after applying the global configuration value, policy routing is disabled for the address family of this connection. Policy routing disabled means that NetworkManager will add all routes to the main table (except static routes that explicitly configure a different table). Additionally, NetworkManager will not delete any extraneous routes from tables except the main table. This is to preserve backward compatibility for users who manage routing tables outside of NetworkManager.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_ROUTES N_("A list of IPv4 destination addresses, prefix length, optional IPv4 next hop addresses, optional route metric, optional attribute. The valid syntax is: \"ip[/prefix] [next-hop] [metric] [attribute=val]...[,ip[/prefix]...]\". For example \"192.0.2.0/24 10.1.1.1 77, 198.51.100.0/24\".")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_ROUTING_RULES N_("A comma separated list of routing rules for policy routing.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE N_("Configure method for creating the address for use with RFC4862 IPv6 Stateless Address Autoconfiguration. The permitted values are: NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64 (0), NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY (1). NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64 (2) or NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT (3). If the property is set to EUI64, the addresses will be generated using the interface tokens derived from hardware address. This makes the host part of the address to stay constant, making it possible to track host's presence when it changes networks. The address changes when the interface hardware is replaced. The value of stable-privacy enables use of cryptographically secure hash of a secret host-specific key along with the connection's stable-id and the network address as specified by RFC7217. This makes it impossible to use the address track host's presence, and makes the address stable when the network interface hardware is replaced. The special values \"default\" and \"default-or-eui64\" will fallback to the global connection default in as documented in NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is \"stable-privacy\" or \"eui64\", respectively. For libnm, the property defaults to \"default\" since 1.40. Previously it defaulted to \"stable-privacy\". On D-Bus, the absence of an addr-gen-mode setting equals \"default\". For keyfile plugin, the absence of the setting on disk means \"default-or-eui64\" so that the property doesn't change on upgrade from older versions. Note that this setting is distinct from the Privacy Extensions as configured by \"ip6-privacy\" property and it does not affect the temporary addresses configured with this option.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE N_("Configure method for creating the IPv6 interface identifer of addresses with RFC4862 IPv6 Stateless Address Autoconfiguration and Link Local addresses. The permitted values are: \"eui64\" (0), \"stable-privacy\" (1), \"default\" (3) or \"default-or-eui64\" (2). If the property is set to \"eui64\", the addresses will be generated using the interface token derived from hardware address. This makes the host part of the address to stay constant, making it possible to track the host's presence when it changes networks. The address changes when the interface hardware is replaced. If a duplicate address is detected, there is also no fallback to generate another address. When configured, the \"ipv6.token\" is used instead of the MAC address to generate addresses for stateless autoconfiguration. If the property is set to \"stable-privacy\", the interface identifier is generated as specified by RFC7217. This works by hashing a host specific key (see NetworkManager(8) manual), the interface name, the connection's \"connection.stable-id\" property and the address prefix.  This improves privacy by making it harder to use the address to track the host's presence and the address is stable when the network interface hardware is replaced. The special values \"default\" and \"default-or-eui64\" will fallback to the global connection default as documented in the NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is \"stable-privacy\" or \"eui64\", respectively. If not specified, when creating a new profile the default is \"default\". Note that this setting is distinct from the Privacy Extensions as configured by \"ip6-privacy\" property and it does not affect the temporary addresses configured with this option.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ADDRESSES N_("A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example \"2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64\". The addresses are listed in decreasing priority, meaning the first address will be the primary address. This can make a difference with IPv6 source address selection (RFC 6724, section 5).")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_AUTO_ROUTE_EXT_GW N_("VPN connections will default to add the route automatically unless this setting is set to FALSE. For other connection types, adding such an automatic route is currently not supported and setting this to TRUE has no effect.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DAD_TIMEOUT N_("Timeout in milliseconds used to check for the presence of duplicate IP addresses on the network.  If an address conflict is detected, the activation will fail.  A zero value means that no duplicate address detection is performed, -1 means the default value (either configuration ipvx.dad-timeout override or zero).  A value greater than zero is a timeout in milliseconds. The property is currently implemented only for IPv4.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_DUID N_("A string containing the DHCPv6 Unique Identifier (DUID) used by the dhcp client to identify itself to DHCPv6 servers (RFC 3315). The DUID is carried in the Client Identifier option. If the property is a hex string ('aa:bb:cc') it is interpreted as a binary DUID and filled as an opaque value in the Client Identifier option. The special value \"lease\" will retrieve the DUID previously used from the lease file belonging to the connection. If no DUID is found and \"dhclient\" is the configured dhcp client, the DUID is searched in the system-wide dhclient lease file. If still no DUID is found, or another dhcp client is used, a global and permanent DUID-UUID (RFC 6355) will be generated based on the machine-id. The special values \"llt\" and \"ll\" will generate a DUID of type LLT or LL (see RFC 3315) based on the current MAC address of the device. In order to try providing a stable DUID-LLT, the time field will contain a constant timestamp that is used globally (for all profiles) and persisted to disk. The special values \"stable-llt\", \"stable-ll\" and \"stable-uuid\" will generate a DUID of the corresponding type, derived from the connection's stable-id and a per-host unique key. You may want to include the \"${DEVICE}\" or \"${MAC}\" specifier in the stable-id, in case this profile gets activated on multiple devices. So, the link-layer address of \"stable-ll\" and \"stable-llt\" will be a generated address derived from the stable id. The DUID-LLT time value in the \"stable-llt\" option will be picked among a static timespan of three years (the upper bound of the interval is the same constant timestamp used in \"llt\"). When the property is unset, the global value provided for \"ipv6.dhcp-duid\" is used. If no global value is provided, the default \"lease\" value is assumed.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_HOSTNAME N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-fqdn\" are mutually exclusive and cannot be set at the same time.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_HOSTNAME_FLAGS N_("Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) and NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE (0x4).  When no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is not set, the standard FQDN flags are set in the request: NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) for IPv4 and NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1) for IPv6. When this property is set to the default value NM_DHCP_HOSTNAME_FLAG_NONE (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also NM_DHCP_HOSTNAME_FLAG_NONE (0x0), then the standard FQDN flags described above are sent in the DHCP requests.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_PD_HINT N_("A IPv6 address followed by a slash and a prefix length. If set, the value is sent to the DHCPv6 server as hint indicating the prefix delegation (IA_PD) we want to receive. To only hint a prefix length without prefix, set the address part to the zero address (for example \"::/60\").")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_REJECT_SERVERS N_("Array of servers from which DHCP offers must be rejected. This property is useful to avoid getting a lease from misconfigured or rogue servers. For DHCPv4, each element must be an IPv4 address, optionally followed by a slash and a prefix length (e.g. \"192.168.122.0/24\"). This property is currently not implemented for DHCPv6.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server. This currently only has effect when using systemd-resolved.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("DNS options for /etc/resolv.conf as described in resolv.conf(5) manual. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\" and \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"no-ip6-dotint\". See the resolv.conf(5) manual. Note that there is a distinction between an unset (default) list and an empty list. In nmcli, to unset the list set the value to \"\". To set an empty list, set it to \" \". Currently, an unset list has the same meaning as an empty list. That might change in the future. The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added. The valid \"ipv4.dns-options\" and \"ipv6.dns-options\" get merged together.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".")
@@ -205,7 +208,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_IGNORE_AUTO_ROUTES N_("When \"method\" is set to \"auto\" and this property to TRUE, automatically configured routes are ignored and only routes specified in the \"routes\" property, if any, are used.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_IP6_PRIVACY N_("Configure IPv6 Privacy Extensions for SLAAC, described in RFC4941.  If enabled, it makes the kernel generate a temporary IPv6 address in addition to the public one generated from MAC address via modified EUI-64.  This enhances privacy, but could cause problems in some applications, on the other hand.  The permitted values are: -1: unknown, 0: disabled, 1: enabled (prefer public address), 2: enabled (prefer temporary addresses). Having a per-connection setting set to \"-1\" (unknown) means fallback to global configuration \"ipv6.ip6-privacy\". If also global configuration is unspecified or set to \"-1\", fallback to read \"/proc/sys/net/ipv6/conf/default/use_tempaddr\". Note that this setting is distinct from the Stable Privacy addresses that can be enabled with the \"addr-gen-mode\" property's \"stable-privacy\" setting as another way of avoiding host tracking with IPv6 addresses.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_MAY_FAIL N_("If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out.  Note that at least one IP configuration must succeed or overall network configuration will still fail.  For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_METHOD N_("IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support \"disabled\", \"auto\", \"manual\", and \"link-local\". See the subclass-specific documentation for other values. In general, for the \"auto\" method, properties such as \"dns\" and \"routes\" specify information that is added on to the information returned from automatic configuration.  The \"ignore-auto-routes\" and \"ignore-auto-dns\" properties modify this behavior. For methods that imply no upstream network, such as \"shared\" or \"link-local\", these properties must be empty. For IPv4 method \"shared\", the IP subnet can be configured by adding one manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the shared method must be configured on the interface which shares the internet to a subnet, not on the uplink which is shared.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_METHOD N_("The IPv6 connection method.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_MTU N_("Maximum transmission unit size, in bytes. If zero (the default), the MTU is set automatically from router advertisements or is left equal to the link-layer MTU. If greater than the link-layer MTU, or greater than zero but less than the minimum IPv6 MTU of 1280, this value has no effect.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_NEVER_DEFAULT N_("If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_RA_TIMEOUT N_("A timeout for waiting Router Advertisements in seconds. If zero (the default), a globally configured default is used. If still unspecified, the timeout depends on the sysctl settings of the device. Set to 2147483647 (MAXINT32) for infinity.")
@@ -215,7 +218,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTE_TABLE N_("Enable policy routing (source routing) and set the routing table used when adding routes. This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes and static routes. But note that static routes can individually overwrite the setting by explicitly specifying a non-zero routing table. If the table setting is left at zero, it is eligible to be overwritten via global configuration. If the property is zero even after applying the global configuration value, policy routing is disabled for the address family of this connection. Policy routing disabled means that NetworkManager will add all routes to the main table (except static routes that explicitly configure a different table). Additionally, NetworkManager will not delete any extraneous routes from tables except the main table. This is to preserve backward compatibility for users who manage routing tables outside of NetworkManager.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTES N_("Array of IP routes.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTING_RULES N_("A comma separated list of routing rules for policy routing.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_TOKEN N_("Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_TOKEN N_("Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode. When set, the token is used as IPv6 interface identifier instead of the hardware address. This only applies to addresses from stateless autoconfiguration, not to IPv6 link local addresses.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT N_("How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels. To disable this option, add NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1) to ip-tunnel flags.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FLAGS N_("Tunnel flags. Currently, the following values are supported: NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS (0x2), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL (0x4), NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV (0x8), NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY (0x10), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK (0x20). They are valid only for IPv6 tunnels.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FLOW_LABEL N_("The flow label to assign to tunnel packets. This property applies only to IPv6 tunnels.")
@@ -406,10 +409,10 @@
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_MODE N_("Wi-Fi network mode; one of \"infrastructure\", \"mesh\", \"adhoc\" or \"ap\".  If blank, infrastructure is assumed.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_POWERSAVE N_("One of NM_SETTING_WIRELESS_POWERSAVE_DISABLE (2) (disable Wi-Fi power saving), NM_SETTING_WIRELESS_POWERSAVE_ENABLE (3) (enable Wi-Fi power saving), NM_SETTING_WIRELESS_POWERSAVE_IGNORE (1) (don't touch currently configure setting) or NM_SETTING_WIRELESS_POWERSAVE_DEFAULT (0) (use the globally configured value). All other values are reserved.")
-#define DESCRIBE_DOC_NM_SETTING_WIRELESS_RATE N_("If non-zero, directs the device to only use the specified bitrate for communication with the access point.  Units are in Kb/s, ie 5500 = 5.5 Mbit/s.  This property is highly driver dependent and not all devices support setting a static bitrate.")
-#define DESCRIBE_DOC_NM_SETTING_WIRELESS_SEEN_BSSIDS N_("A list of BSSIDs (each BSSID formatted as a MAC address like \"00:11:22:33:44:55\") that have been detected as part of the Wi-Fi network.  NetworkManager internally tracks previously seen BSSIDs. The property is only meant for reading and reflects the BSSID list of NetworkManager. The changes you make to this property will not be preserved.")
+#define DESCRIBE_DOC_NM_SETTING_WIRELESS_RATE N_("This property is not implemented and has no effect.")
+#define DESCRIBE_DOC_NM_SETTING_WIRELESS_SEEN_BSSIDS N_("A list of BSSIDs (each BSSID formatted as a MAC address like \"00:11:22:33:44:55\") that have been detected as part of the Wi-Fi network.  NetworkManager internally tracks previously seen BSSIDs. The property is only meant for reading and reflects the BSSID list of NetworkManager. The changes you make to this property will not be preserved. This is not a regular property that the user would configure. Instead, NetworkManager automatically sets the seen BSSIDs and tracks them internally in \"/var/lib/NetworkManager/seen-bssids\" file.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_SSID N_("SSID of the Wi-Fi network. Must be specified.")
-#define DESCRIBE_DOC_NM_SETTING_WIRELESS_TX_POWER N_("If non-zero, directs the device to use the specified transmit power. Units are dBm.  This property is highly driver dependent and not all devices support setting a static transmit power.")
+#define DESCRIBE_DOC_NM_SETTING_WIRELESS_TX_POWER N_("This property is not implemented and has no effect.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_WAKE_ON_WLAN N_("The NMSettingWirelessWakeOnWLan options to enable. Not all devices support all options. May be any combination of NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY (0x2), NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT (0x4), NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC (0x8), NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE (0x10), NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST (0x20), NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE (0x40), NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE (0x80), NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP (0x100) or the special values NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT (0x1) (to use global settings) and NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE (0x8000) (to disable management of Wake-on-LAN in NetworkManager).")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_SECURITY_AUTH_ALG N_("When WEP is used (ie, key-mgmt = \"none\" or \"ieee8021x\") indicate the 802.11 authentication algorithm required by the AP here.  One of \"open\" for Open System, \"shared\" for Shared Key, or \"leap\" for Cisco LEAP.  When using Cisco LEAP (ie, key-mgmt = \"ieee8021x\" and auth-alg = \"leap\") the \"leap-username\" and \"leap-password\" properties must be specified.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_SECURITY_FILS N_("Indicates whether Fast Initial Link Setup (802.11ai) must be enabled for the connection.  One of NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (0) (use global default value), NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE (1) (disable FILS), NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL (2) (enable FILS if the supplicant and the access point support it) or NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED (3) (enable FILS and fail if not supported).  When set to NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (0) and no global default is set, FILS will be optionally enabled.")
@@ -442,6 +445,10 @@
 #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP N_("Whether the system hostname can be determined from reverse DNS lookup of addresses on this device. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn't have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_TRUE (1).")
 #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT N_("If set to NM_TERNARY_TRUE (1), NetworkManager attempts to get the hostname via DHCPv4/DHCPv6 or reverse DNS lookup on this device only when the device has the default route for the given address family (IPv4/IPv6). If set to NM_TERNARY_FALSE (0), the hostname can be set from this device even if it doesn't have the default route. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn't have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_FALSE (0).")
 #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_PRIORITY N_("The relative priority of this connection to determine the system hostname. A lower numerical value is better (higher priority).  A connection with higher priority is considered before connections with lower priority. If the value is zero, it can be overridden by a global value from NetworkManager configuration. If the property doesn't have a value in the global configuration, the value is assumed to be 100. Negative values have the special effect of excluding other connections with a greater numerical priority value; so in presence of at least one negative priority, only connections with the lowest priority value will be used to determine the hostname.")
+#define DESCRIBE_DOC_NM_SETTING_LINK_GRO_MAX_SIZE N_("The maximum size of a packet built by the Generic Receive Offload stack for this device. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved.")
+#define DESCRIBE_DOC_NM_SETTING_LINK_GSO_MAX_SEGMENTS N_("The maximum segments of a Generic Segment Offload packet the device should accept. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved.")
+#define DESCRIBE_DOC_NM_SETTING_LINK_GSO_MAX_SIZE N_("The maximum size of a Generic Segment Offload packet the device should accept. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved.")
+#define DESCRIBE_DOC_NM_SETTING_LINK_TX_QUEUE_LENGTH N_("The size of the transmit queue for the device, in number of packets. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved.")
 #define DESCRIBE_DOC_NM_SETTING_LOOPBACK_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames.")
 #define DESCRIBE_DOC_NM_SETTING_OVS_EXTERNAL_IDS_DATA N_("A dictionary of key/value pairs with external-ids for OVS.")
 #define DESCRIBE_DOC_NM_SETTING_OVS_OTHER_CONFIG_DATA N_("A dictionary of key/value pairs with other_config settings for OVS. See also \"other_config\" in the \"ovs-vswitchd.conf.db\" manual for the keys that OVS supports.")
diff --git a/src/libnmc-setting/settings-docs.h.in b/src/libnmc-setting/settings-docs.h.in
index 0b2f8eca..f06c981a 100644
--- a/src/libnmc-setting/settings-docs.h.in
+++ b/src/libnmc-setting/settings-docs.h.in
@@ -18,10 +18,10 @@
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_MUD_URL N_("If configured, set to a Manufacturer Usage Description (MUD) URL that points to manufacturer-recommended network policies for IoT devices. It is transmitted as a DHCPv4 or DHCPv6 option. The value must be a valid URL starting with \"https://\". The special value \"none\" is allowed to indicate that no MUD URL is used. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the ultimate default is \"none\".")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_MULTI_CONNECT N_("Specifies whether the profile can be active multiple times at a particular moment. The value is of type NMConnectionMultiConnect.")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_PERMISSIONS N_("An array of strings defining what access a given user has to this connection.  If this is NULL or empty, all users are allowed to access this connection; otherwise users are allowed if and only if they are in this list.  When this is not empty, the connection can be active only when one of the specified users is logged into an active session.  Each entry is of the form \"[type]:[id]:[reserved]\"; for example, \"user:dcbw:blah\". At this time only the \"user\" [type] is allowed.  Any other values are ignored and reserved for future use.  [id] is the username that this permission refers to, which may not contain the \":\" character. Any [reserved] information present must be ignored and is reserved for future use.  All of [type], [id], and [reserved] must be valid UTF-8.")
-#define DESCRIBE_DOC_NM_SETTING_CONNECTION_READ_ONLY N_("FALSE if the connection can be modified using the provided settings service's D-Bus interface with the right privileges, or TRUE if the connection is read-only and cannot be modified.")
+#define DESCRIBE_DOC_NM_SETTING_CONNECTION_READ_ONLY N_("This property is deprecated and has no meaning.")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_SECONDARIES N_("List of connection UUIDs that should be activated when the base connection itself is activated. Currently, only VPN connections are supported.")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_SLAVE_TYPE N_("Setting name of the device type of this slave's master connection (eg, \"bond\"), or NULL if this connection is not a slave.")
-#define DESCRIBE_DOC_NM_SETTING_CONNECTION_STABLE_ID N_("This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable and to derive the DHCP DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid]. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device's name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key. See NetworkManager(8) manual about the secret-key and the host identity. The '$' character is treated special to perform dynamic substitutions at runtime. Currently, supported are \"${CONNECTION}\", \"${DEVICE}\", \"${MAC}\", \"${BOOT}\", \"${RANDOM}\". These effectively create unique IDs per-connection, per-device, per-boot, or every time. Note that \"${DEVICE}\" corresponds to the interface name of the device and \"${MAC}\" is the permanent MAC address of the device. Any unrecognized patterns following '$' are treated verbatim, however are reserved for future use. You are thus advised to avoid '$' or escape it as \"$$\". For example, set it to \"${CONNECTION}-${BOOT}-${DEVICE}\" to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is similar to \"${CONNECTION}\" and uses a unique, fixed ID for the connection.")
+#define DESCRIBE_DOC_NM_SETTING_CONNECTION_STABLE_ID N_("This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used to derive the DHCP client identifier with ipv4.dhcp-client-id=stable, the DHCPv6 DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid] and the DHCP IAID with ipv4.iaid=stable and ipv6.iaid=stable. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device's name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key. See NetworkManager(8) manual about the secret-key and the host identity. The '$' character is treated special to perform dynamic substitutions at activation time. Currently, supported are \"${CONNECTION}\", \"${DEVICE}\", \"${MAC}\", \"${BOOT}\", \"${RANDOM}\".  These effectively create unique IDs per-connection, per-device, per-boot, or every time. The \"${CONNECTION}\" uses the profile's connection.uuid, the \"${DEVICE}\" uses the interface name of the device and \"${MAC}\" the permanent MAC address of the device. Any unrecognized patterns following '$' are treated verbatim, however are reserved for future use. You are thus advised to avoid '$' or escape it as \"$$\".  For example, set it to \"${CONNECTION}-${BOOT}-${DEVICE}\" to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is \"default${CONNECTION}\" go generate an ID unique per connection profile.")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_TIMESTAMP N_("The time, in seconds since the Unix Epoch, that the connection was last _successfully_ fully activated. NetworkManager updates the connection timestamp periodically when the connection is active to ensure that an active connection has the latest timestamp. The property is only meant for reading (changes to this property will not be preserved).")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_TYPE N_("Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, \"802-3-ethernet\" or \"802-11-wireless\" or \"bluetooth\", etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, \"vpn\" or \"bridge\", etc).")
 #define DESCRIBE_DOC_NM_SETTING_CONNECTION_UUID N_("The connection.uuid is the real identifier of a profile. It cannot change and it must be unique. It is therefore often best to refer to a profile by UUID, for example with `nmcli connection up uuid $UUID`. The UUID cannot be changed, except in offline mode. In that case, the special values \"new\", \"generate\" and \"\" are allowed to generate a new random UUID.")
@@ -141,6 +141,8 @@
 #define DESCRIBE_DOC_NM_SETTING_GSM_AUTO_CONFIG N_("When TRUE, the settings such as APN, username, or password will default to values that match the network the modem will register to in the Mobile Broadband Provider database.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_DEVICE_ID N_("The device unique identifier (as given by the WWAN management service) which this connection applies to.  If given, the connection will only apply to the specified device.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_HOME_ONLY N_("When TRUE, only connections to the home network will be allowed. Connections to roaming networks will not be made.")
+#define DESCRIBE_DOC_NM_SETTING_GSM_INITIAL_EPS_BEARER_APN N_("For LTE modems, this sets the APN for the initial EPS bearer that is set up when attaching to the network.  Setting this parameter implies initial-eps-bearer-configure to be TRUE.")
+#define DESCRIBE_DOC_NM_SETTING_GSM_INITIAL_EPS_BEARER_CONFIGURE N_("For LTE modems, this setting determines whether the initial EPS bearer shall be configured when bringing up the connection.  It is inferred TRUE if initial-eps-bearer-apn is set.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_NETWORK_ID N_("The Network ID (GSM LAI format, ie MCC-MNC) to force specific network registration.  If the Network ID is specified, NetworkManager will attempt to force the device to register only on the specified network. This can be used to ensure that the device does not roam when direct roaming control of the device is not otherwise possible.")
 #define DESCRIBE_DOC_NM_SETTING_GSM_NUMBER N_("Legacy setting that used to help establishing PPP data sessions for GSM-based modems.")
@@ -163,13 +165,13 @@
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_FQDN N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified FQDN will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-hostname\" are mutually exclusive and cannot be set at the same time.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_HOSTNAME N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-fqdn\" are mutually exclusive and cannot be set at the same time.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_HOSTNAME_FLAGS N_("Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) and NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE (0x4).  When no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is not set, the standard FQDN flags are set in the request: NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) for IPv4 and NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1) for IPv6. When this property is set to the default value NM_DHCP_HOSTNAME_FLAG_NONE (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also NM_DHCP_HOSTNAME_FLAG_NONE (0x0), then the standard FQDN flags described above are sent in the DHCP requests.")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_REJECT_SERVERS N_("Array of servers from which DHCP offers must be rejected. This property is useful to avoid getting a lease from misconfigured or rogue servers. For DHCPv4, each element must be an IPv4 address, optionally followed by a slash and a prefix length (e.g. \"192.168.122.0/24\"). This property is currently not implemented for DHCPv6.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER N_("The Vendor Class Identifier DHCP option (60). Special characters in the data string may be escaped using C-style escapes, nevertheless this property cannot contain nul bytes. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the DHCP option is not sent to the server.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server. This currently only has effect when using systemd-resolved.")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_OPTIONS N_("DNS options for /etc/resolv.conf as described in resolv.conf(5) manual. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". See the resolv.conf(5) manual. Note that there is a distinction between an unset (default) list and an empty list. In nmcli, to unset the list set the value to \"\". To set an empty list, set it to \" \". Currently, an unset list has the same meaning as an empty list. That might change in the future. The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added. The valid \"ipv4.dns-options\" and \"ipv6.dns-options\" get merged together.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".")
@@ -177,7 +179,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_IGNORE_AUTO_ROUTES N_("When \"method\" is set to \"auto\" and this property to TRUE, automatically configured routes are ignored and only routes specified in the \"routes\" property, if any, are used.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_LINK_LOCAL N_("Enable and disable the IPv4 link-local configuration independently of the ipv4.method configuration. This allows a link-local address (169.254.x.y/16) to be obtained in addition to other addresses, such as those manually configured or obtained from a DHCP server. When set to \"auto\", the value is dependent on \"ipv4.method\". When set to \"default\", it honors the global connection default, before falling back to \"auto\". Note that if \"ipv4.method\" is \"disabled\", then link local addressing is always disabled too. The default is \"default\".")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_MAY_FAIL N_("If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out.  Note that at least one IP configuration must succeed or overall network configuration will still fail.  For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully.")
-#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_METHOD N_("IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support \"disabled\", \"auto\", \"manual\", and \"link-local\". See the subclass-specific documentation for other values. In general, for the \"auto\" method, properties such as \"dns\" and \"routes\" specify information that is added on to the information returned from automatic configuration.  The \"ignore-auto-routes\" and \"ignore-auto-dns\" properties modify this behavior. For methods that imply no upstream network, such as \"shared\" or \"link-local\", these properties must be empty. For IPv4 method \"shared\", the IP subnet can be configured by adding one manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the shared method must be configured on the interface which shares the internet to a subnet, not on the uplink which is shared.")
+#define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_METHOD N_("The IPv4 connection method.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_NEVER_DEFAULT N_("If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_REPLACE_LOCAL_RULE N_("Connections will default to keep the autogenerated priority 0 local rule unless this setting is set to TRUE.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_REQUIRED_TIMEOUT N_("The minimum time interval in milliseconds for which dynamic IP configuration should be tried before the connection succeeds. This property is useful for example if both IPv4 and IPv6 are enabled and are allowed to fail. Normally the connection succeeds as soon as one of the two address families completes; by setting a required timeout for e.g. IPv4, one can ensure that even if IP6 succeeds earlier than IPv4, NetworkManager waits some time for IPv4 before the connection becomes active. Note that if \"may-fail\" is FALSE for the same address family, this property has no effect as NetworkManager needs to wait for the full DHCP timeout. A zero value means that no required timeout is present, -1 means the default value (either configuration ipvx.required-timeout override or zero).")
@@ -185,19 +187,20 @@
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_ROUTE_TABLE N_("Enable policy routing (source routing) and set the routing table used when adding routes. This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes and static routes. But note that static routes can individually overwrite the setting by explicitly specifying a non-zero routing table. If the table setting is left at zero, it is eligible to be overwritten via global configuration. If the property is zero even after applying the global configuration value, policy routing is disabled for the address family of this connection. Policy routing disabled means that NetworkManager will add all routes to the main table (except static routes that explicitly configure a different table). Additionally, NetworkManager will not delete any extraneous routes from tables except the main table. This is to preserve backward compatibility for users who manage routing tables outside of NetworkManager.")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_ROUTES N_("A list of IPv4 destination addresses, prefix length, optional IPv4 next hop addresses, optional route metric, optional attribute. The valid syntax is: \"ip[/prefix] [next-hop] [metric] [attribute=val]...[,ip[/prefix]...]\". For example \"192.0.2.0/24 10.1.1.1 77, 198.51.100.0/24\".")
 #define DESCRIBE_DOC_NM_SETTING_IP4_CONFIG_ROUTING_RULES N_("A comma separated list of routing rules for policy routing.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE N_("Configure method for creating the address for use with RFC4862 IPv6 Stateless Address Autoconfiguration. The permitted values are: NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64 (0), NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY (1). NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64 (2) or NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT (3). If the property is set to EUI64, the addresses will be generated using the interface tokens derived from hardware address. This makes the host part of the address to stay constant, making it possible to track host's presence when it changes networks. The address changes when the interface hardware is replaced. The value of stable-privacy enables use of cryptographically secure hash of a secret host-specific key along with the connection's stable-id and the network address as specified by RFC7217. This makes it impossible to use the address track host's presence, and makes the address stable when the network interface hardware is replaced. The special values \"default\" and \"default-or-eui64\" will fallback to the global connection default in as documented in NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is \"stable-privacy\" or \"eui64\", respectively. For libnm, the property defaults to \"default\" since 1.40. Previously it defaulted to \"stable-privacy\". On D-Bus, the absence of an addr-gen-mode setting equals \"default\". For keyfile plugin, the absence of the setting on disk means \"default-or-eui64\" so that the property doesn't change on upgrade from older versions. Note that this setting is distinct from the Privacy Extensions as configured by \"ip6-privacy\" property and it does not affect the temporary addresses configured with this option.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE N_("Configure method for creating the IPv6 interface identifer of addresses with RFC4862 IPv6 Stateless Address Autoconfiguration and Link Local addresses. The permitted values are: \"eui64\" (0), \"stable-privacy\" (1), \"default\" (3) or \"default-or-eui64\" (2). If the property is set to \"eui64\", the addresses will be generated using the interface token derived from hardware address. This makes the host part of the address to stay constant, making it possible to track the host's presence when it changes networks. The address changes when the interface hardware is replaced. If a duplicate address is detected, there is also no fallback to generate another address. When configured, the \"ipv6.token\" is used instead of the MAC address to generate addresses for stateless autoconfiguration. If the property is set to \"stable-privacy\", the interface identifier is generated as specified by RFC7217. This works by hashing a host specific key (see NetworkManager(8) manual), the interface name, the connection's \"connection.stable-id\" property and the address prefix.  This improves privacy by making it harder to use the address to track the host's presence and the address is stable when the network interface hardware is replaced. The special values \"default\" and \"default-or-eui64\" will fallback to the global connection default as documented in the NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is \"stable-privacy\" or \"eui64\", respectively. If not specified, when creating a new profile the default is \"default\". Note that this setting is distinct from the Privacy Extensions as configured by \"ip6-privacy\" property and it does not affect the temporary addresses configured with this option.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ADDRESSES N_("A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example \"2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64\". The addresses are listed in decreasing priority, meaning the first address will be the primary address. This can make a difference with IPv6 source address selection (RFC 6724, section 5).")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_AUTO_ROUTE_EXT_GW N_("VPN connections will default to add the route automatically unless this setting is set to FALSE. For other connection types, adding such an automatic route is currently not supported and setting this to TRUE has no effect.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DAD_TIMEOUT N_("Timeout in milliseconds used to check for the presence of duplicate IP addresses on the network.  If an address conflict is detected, the activation will fail.  A zero value means that no duplicate address detection is performed, -1 means the default value (either configuration ipvx.dad-timeout override or zero).  A value greater than zero is a timeout in milliseconds. The property is currently implemented only for IPv4.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_DUID N_("A string containing the DHCPv6 Unique Identifier (DUID) used by the dhcp client to identify itself to DHCPv6 servers (RFC 3315). The DUID is carried in the Client Identifier option. If the property is a hex string ('aa:bb:cc') it is interpreted as a binary DUID and filled as an opaque value in the Client Identifier option. The special value \"lease\" will retrieve the DUID previously used from the lease file belonging to the connection. If no DUID is found and \"dhclient\" is the configured dhcp client, the DUID is searched in the system-wide dhclient lease file. If still no DUID is found, or another dhcp client is used, a global and permanent DUID-UUID (RFC 6355) will be generated based on the machine-id. The special values \"llt\" and \"ll\" will generate a DUID of type LLT or LL (see RFC 3315) based on the current MAC address of the device. In order to try providing a stable DUID-LLT, the time field will contain a constant timestamp that is used globally (for all profiles) and persisted to disk. The special values \"stable-llt\", \"stable-ll\" and \"stable-uuid\" will generate a DUID of the corresponding type, derived from the connection's stable-id and a per-host unique key. You may want to include the \"${DEVICE}\" or \"${MAC}\" specifier in the stable-id, in case this profile gets activated on multiple devices. So, the link-layer address of \"stable-ll\" and \"stable-llt\" will be a generated address derived from the stable id. The DUID-LLT time value in the \"stable-llt\" option will be picked among a static timespan of three years (the upper bound of the interval is the same constant timestamp used in \"llt\"). When the property is unset, the global value provided for \"ipv6.dhcp-duid\" is used. If no global value is provided, the default \"lease\" value is assumed.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_HOSTNAME N_("If the \"dhcp-send-hostname\" property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and \"dhcp-fqdn\" are mutually exclusive and cannot be set at the same time.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_HOSTNAME_FLAGS N_("Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) and NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE (0x4).  When no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is not set, the standard FQDN flags are set in the request: NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) for IPv4 and NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1) for IPv6. When this property is set to the default value NM_DHCP_HOSTNAME_FLAG_NONE (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also NM_DHCP_HOSTNAME_FLAG_NONE (0x0), then the standard FQDN flags described above are sent in the DHCP requests.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_IAID N_("A string containing the \"Identity Association Identifier\" (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or as colon separated hexadecimal numbers). Alternatively it can be set to the special values \"mac\", \"perm-mac\", \"ifname\" or \"stable\". When set to \"mac\" (or \"perm-mac\"), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to \"ifname\", the IAID is computed by hashing the interface name. The special value \"stable\" can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be \"ifname\". For DHCPv4, the IAID is only used with \"ipv4.dhcp-client-id\" values \"duid\" and \"ipv6-duid\" to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the \"internal\" DHCPv6 plugin. The \"dhclient\" DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_PD_HINT N_("A IPv6 address followed by a slash and a prefix length. If set, the value is sent to the DHCPv6 server as hint indicating the prefix delegation (IA_PD) we want to receive. To only hint a prefix length without prefix, set the address part to the zero address (for example \"::/60\").")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_REJECT_SERVERS N_("Array of servers from which DHCP offers must be rejected. This property is useful to avoid getting a lease from misconfigured or rogue servers. For DHCPv4, each element must be an IPv4 address, optionally followed by a slash and a prefix length (e.g. \"192.168.122.0/24\"). This property is currently not implemented for DHCPv6.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_SEND_HOSTNAME N_("If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the \"dhcp-hostname\" property is NULL and this property is TRUE, the current persistent hostname of the computer is sent.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DHCP_TIMEOUT N_("A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS N_("Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending \"#example.com\" to the IP address of the DNS server. This currently only has effect when using systemd-resolved.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-ip6-dotint\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\". The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_OPTIONS N_("DNS options for /etc/resolv.conf as described in resolv.conf(5) manual. The currently supported options are \"attempts\", \"debug\", \"edns0\", \"ndots\", \"no-aaaa\", \"no-check-names\", \"no-reload\", \"no-tld-query\", \"rotate\", \"single-request\", \"single-request-reopen\", \"timeout\", \"trust-ad\", \"use-vc\" and \"inet6\", \"ip6-bytestring\", \"ip6-dotint\", \"no-ip6-dotint\". See the resolv.conf(5) manual. Note that there is a distinction between an unset (default) list and an empty list. In nmcli, to unset the list set the value to \"\". To set an empty list, set it to \" \". Currently, an unset list has the same meaning as an empty list. That might change in the future. The \"trust-ad\" setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have \"trust-ad\" enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then \"edns0\" and \"trust-ad\" are automatically added. The valid \"ipv4.dns-options\" and \"ipv6.dns-options\" get merged together.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_PRIORITY N_("DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the \"rotate\" option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the '~.' special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_DNS_SEARCH N_("List of DNS search domains. Domains starting with a tilde ('~') are considered 'routing' domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting \"ignore-auto-dns\". Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15).")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_GATEWAY N_("The gateway associated with this configuration. This is only meaningful if \"addresses\" is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if \"never-default\" is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See \"ip4-auto-default-route\".")
@@ -205,7 +208,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_IGNORE_AUTO_ROUTES N_("When \"method\" is set to \"auto\" and this property to TRUE, automatically configured routes are ignored and only routes specified in the \"routes\" property, if any, are used.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_IP6_PRIVACY N_("Configure IPv6 Privacy Extensions for SLAAC, described in RFC4941.  If enabled, it makes the kernel generate a temporary IPv6 address in addition to the public one generated from MAC address via modified EUI-64.  This enhances privacy, but could cause problems in some applications, on the other hand.  The permitted values are: -1: unknown, 0: disabled, 1: enabled (prefer public address), 2: enabled (prefer temporary addresses). Having a per-connection setting set to \"-1\" (unknown) means fallback to global configuration \"ipv6.ip6-privacy\". If also global configuration is unspecified or set to \"-1\", fallback to read \"/proc/sys/net/ipv6/conf/default/use_tempaddr\". Note that this setting is distinct from the Stable Privacy addresses that can be enabled with the \"addr-gen-mode\" property's \"stable-privacy\" setting as another way of avoiding host tracking with IPv6 addresses.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_MAY_FAIL N_("If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out.  Note that at least one IP configuration must succeed or overall network configuration will still fail.  For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_METHOD N_("IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support \"disabled\", \"auto\", \"manual\", and \"link-local\". See the subclass-specific documentation for other values. In general, for the \"auto\" method, properties such as \"dns\" and \"routes\" specify information that is added on to the information returned from automatic configuration.  The \"ignore-auto-routes\" and \"ignore-auto-dns\" properties modify this behavior. For methods that imply no upstream network, such as \"shared\" or \"link-local\", these properties must be empty. For IPv4 method \"shared\", the IP subnet can be configured by adding one manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the shared method must be configured on the interface which shares the internet to a subnet, not on the uplink which is shared.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_METHOD N_("The IPv6 connection method.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_MTU N_("Maximum transmission unit size, in bytes. If zero (the default), the MTU is set automatically from router advertisements or is left equal to the link-layer MTU. If greater than the link-layer MTU, or greater than zero but less than the minimum IPv6 MTU of 1280, this value has no effect.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_NEVER_DEFAULT N_("If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_RA_TIMEOUT N_("A timeout for waiting Router Advertisements in seconds. If zero (the default), a globally configured default is used. If still unspecified, the timeout depends on the sysctl settings of the device. Set to 2147483647 (MAXINT32) for infinity.")
@@ -215,7 +218,7 @@
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTE_TABLE N_("Enable policy routing (source routing) and set the routing table used when adding routes. This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes and static routes. But note that static routes can individually overwrite the setting by explicitly specifying a non-zero routing table. If the table setting is left at zero, it is eligible to be overwritten via global configuration. If the property is zero even after applying the global configuration value, policy routing is disabled for the address family of this connection. Policy routing disabled means that NetworkManager will add all routes to the main table (except static routes that explicitly configure a different table). Additionally, NetworkManager will not delete any extraneous routes from tables except the main table. This is to preserve backward compatibility for users who manage routing tables outside of NetworkManager.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTES N_("Array of IP routes.")
 #define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_ROUTING_RULES N_("A comma separated list of routing rules for policy routing.")
-#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_TOKEN N_("Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode.")
+#define DESCRIBE_DOC_NM_SETTING_IP6_CONFIG_TOKEN N_("Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode. When set, the token is used as IPv6 interface identifier instead of the hardware address. This only applies to addresses from stateless autoconfiguration, not to IPv6 link local addresses.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_ENCAPSULATION_LIMIT N_("How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels. To disable this option, add NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1) to ip-tunnel flags.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FLAGS N_("Tunnel flags. Currently, the following values are supported: NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS (0x2), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL (0x4), NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV (0x8), NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY (0x10), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK (0x20). They are valid only for IPv6 tunnels.")
 #define DESCRIBE_DOC_NM_SETTING_IP_TUNNEL_FLOW_LABEL N_("The flow label to assign to tunnel packets. This property applies only to IPv6 tunnels.")
@@ -406,10 +409,10 @@
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_MODE N_("Wi-Fi network mode; one of \"infrastructure\", \"mesh\", \"adhoc\" or \"ap\".  If blank, infrastructure is assumed.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_POWERSAVE N_("One of NM_SETTING_WIRELESS_POWERSAVE_DISABLE (2) (disable Wi-Fi power saving), NM_SETTING_WIRELESS_POWERSAVE_ENABLE (3) (enable Wi-Fi power saving), NM_SETTING_WIRELESS_POWERSAVE_IGNORE (1) (don't touch currently configure setting) or NM_SETTING_WIRELESS_POWERSAVE_DEFAULT (0) (use the globally configured value). All other values are reserved.")
-#define DESCRIBE_DOC_NM_SETTING_WIRELESS_RATE N_("If non-zero, directs the device to only use the specified bitrate for communication with the access point.  Units are in Kb/s, ie 5500 = 5.5 Mbit/s.  This property is highly driver dependent and not all devices support setting a static bitrate.")
-#define DESCRIBE_DOC_NM_SETTING_WIRELESS_SEEN_BSSIDS N_("A list of BSSIDs (each BSSID formatted as a MAC address like \"00:11:22:33:44:55\") that have been detected as part of the Wi-Fi network.  NetworkManager internally tracks previously seen BSSIDs. The property is only meant for reading and reflects the BSSID list of NetworkManager. The changes you make to this property will not be preserved.")
+#define DESCRIBE_DOC_NM_SETTING_WIRELESS_RATE N_("This property is not implemented and has no effect.")
+#define DESCRIBE_DOC_NM_SETTING_WIRELESS_SEEN_BSSIDS N_("A list of BSSIDs (each BSSID formatted as a MAC address like \"00:11:22:33:44:55\") that have been detected as part of the Wi-Fi network.  NetworkManager internally tracks previously seen BSSIDs. The property is only meant for reading and reflects the BSSID list of NetworkManager. The changes you make to this property will not be preserved. This is not a regular property that the user would configure. Instead, NetworkManager automatically sets the seen BSSIDs and tracks them internally in \"/var/lib/NetworkManager/seen-bssids\" file.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_SSID N_("SSID of the Wi-Fi network. Must be specified.")
-#define DESCRIBE_DOC_NM_SETTING_WIRELESS_TX_POWER N_("If non-zero, directs the device to use the specified transmit power. Units are dBm.  This property is highly driver dependent and not all devices support setting a static transmit power.")
+#define DESCRIBE_DOC_NM_SETTING_WIRELESS_TX_POWER N_("This property is not implemented and has no effect.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_WAKE_ON_WLAN N_("The NMSettingWirelessWakeOnWLan options to enable. Not all devices support all options. May be any combination of NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY (0x2), NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT (0x4), NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC (0x8), NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE (0x10), NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST (0x20), NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE (0x40), NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE (0x80), NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP (0x100) or the special values NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT (0x1) (to use global settings) and NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE (0x8000) (to disable management of Wake-on-LAN in NetworkManager).")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_SECURITY_AUTH_ALG N_("When WEP is used (ie, key-mgmt = \"none\" or \"ieee8021x\") indicate the 802.11 authentication algorithm required by the AP here.  One of \"open\" for Open System, \"shared\" for Shared Key, or \"leap\" for Cisco LEAP.  When using Cisco LEAP (ie, key-mgmt = \"ieee8021x\" and auth-alg = \"leap\") the \"leap-username\" and \"leap-password\" properties must be specified.")
 #define DESCRIBE_DOC_NM_SETTING_WIRELESS_SECURITY_FILS N_("Indicates whether Fast Initial Link Setup (802.11ai) must be enabled for the connection.  One of NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (0) (use global default value), NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE (1) (disable FILS), NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL (2) (enable FILS if the supplicant and the access point support it) or NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED (3) (enable FILS and fail if not supported).  When set to NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (0) and no global default is set, FILS will be optionally enabled.")
@@ -442,6 +445,10 @@
 #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_FROM_DNS_LOOKUP N_("Whether the system hostname can be determined from reverse DNS lookup of addresses on this device. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn't have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_TRUE (1).")
 #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_ONLY_FROM_DEFAULT N_("If set to NM_TERNARY_TRUE (1), NetworkManager attempts to get the hostname via DHCPv4/DHCPv6 or reverse DNS lookup on this device only when the device has the default route for the given address family (IPv4/IPv6). If set to NM_TERNARY_FALSE (0), the hostname can be set from this device even if it doesn't have the default route. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn't have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_FALSE (0).")
 #define DESCRIBE_DOC_NM_SETTING_HOSTNAME_PRIORITY N_("The relative priority of this connection to determine the system hostname. A lower numerical value is better (higher priority).  A connection with higher priority is considered before connections with lower priority. If the value is zero, it can be overridden by a global value from NetworkManager configuration. If the property doesn't have a value in the global configuration, the value is assumed to be 100. Negative values have the special effect of excluding other connections with a greater numerical priority value; so in presence of at least one negative priority, only connections with the lowest priority value will be used to determine the hostname.")
+#define DESCRIBE_DOC_NM_SETTING_LINK_GRO_MAX_SIZE N_("The maximum size of a packet built by the Generic Receive Offload stack for this device. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved.")
+#define DESCRIBE_DOC_NM_SETTING_LINK_GSO_MAX_SEGMENTS N_("The maximum segments of a Generic Segment Offload packet the device should accept. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved.")
+#define DESCRIBE_DOC_NM_SETTING_LINK_GSO_MAX_SIZE N_("The maximum size of a Generic Segment Offload packet the device should accept. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved.")
+#define DESCRIBE_DOC_NM_SETTING_LINK_TX_QUEUE_LENGTH N_("The size of the transmit queue for the device, in number of packets. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved.")
 #define DESCRIBE_DOC_NM_SETTING_LOOPBACK_MTU N_("If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames.")
 #define DESCRIBE_DOC_NM_SETTING_OVS_EXTERNAL_IDS_DATA N_("A dictionary of key/value pairs with external-ids for OVS.")
 #define DESCRIBE_DOC_NM_SETTING_OVS_OTHER_CONFIG_DATA N_("A dictionary of key/value pairs with other_config settings for OVS. See also \"other_config\" in the \"ovs-vswitchd.conf.db\" manual for the keys that OVS supports.")
diff --git a/src/libnmt-newt/nmt-newt-form.c b/src/libnmt-newt/nmt-newt-form.c
index de80b42a..6e0589cc 100644
--- a/src/libnmt-newt/nmt-newt-form.c
+++ b/src/libnmt-newt/nmt-newt-form.c
@@ -70,7 +70,7 @@ static void nmt_newt_form_redraw(NmtNewtForm *form);
 
 /**
  * nmt_newt_form_new:
- * @title: (allow-none): the form title
+ * @title: (nullable): the form title
  *
  * Creates a new form, which will be shown centered on the screen.
  * Compare nmt_newt_form_new_fullscreen(). You can also position a
@@ -91,7 +91,7 @@ nmt_newt_form_new(const char *title)
 
 /**
  * nmt_newt_form_new_fullscreen:
- * @title: (allow-none): the form title
+ * @title: (nullable): the form title
  *
  * Creates a new fullscreen form. Compare nmt_newt_form_new().
  *
diff --git a/src/libnmt-newt/nmt-newt-listbox.c b/src/libnmt-newt/nmt-newt-listbox.c
index 96e04c15..0a27e358 100644
--- a/src/libnmt-newt/nmt-newt-listbox.c
+++ b/src/libnmt-newt/nmt-newt-listbox.c
@@ -81,7 +81,7 @@ nmt_newt_listbox_new(int height, NmtNewtListboxFlags flags)
  * nmt_newt_listbox_append:
  * @listbox: an #NmtNewtListbox
  * @entry: the text for the new row
- * @key: (allow-none): the key associated with @entry
+ * @key: (nullable): the key associated with @entry
  *
  * Adds a row to @listbox.
  */
diff --git a/src/libnmt-newt/nmt-newt-widget.c b/src/libnmt-newt/nmt-newt-widget.c
index 8b5647aa..2f17d8f2 100644
--- a/src/libnmt-newt/nmt-newt-widget.c
+++ b/src/libnmt-newt/nmt-newt-widget.c
@@ -171,7 +171,7 @@ nmt_newt_widget_get_components(NmtNewtWidget *widget)
  *
  * Finds the widget inside @widget that owns @co.
  *
- * Return value: @co's owner, or %NULL if it was not found.
+ * Returns: @co's owner, or %NULL if it was not found.
  */
 NmtNewtWidget *
 nmt_newt_widget_find_component(NmtNewtWidget *widget, newtComponent co)
diff --git a/src/n-dhcp4/src/n-dhcp4-c-connection.c b/src/n-dhcp4/src/n-dhcp4-c-connection.c
index 65328286..5abd6f59 100644
--- a/src/n-dhcp4/src/n-dhcp4-c-connection.c
+++ b/src/n-dhcp4/src/n-dhcp4-c-connection.c
@@ -152,6 +152,7 @@ int n_dhcp4_c_connection_listen(NDhcp4CConnection *connection) {
         if (connection->fd_packet >= 0) {
                 epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL);
                 connection->fd_packet = c_close(connection->fd_packet);
+                connection->ns_drain_timeout = 0;
         }
 
         if (connection->fd_udp >= 0) {
@@ -211,6 +212,7 @@ int n_dhcp4_c_connection_connect(NDhcp4CConnection *connection,
         }
 
         connection->state = N_DHCP4_C_CONNECTION_STATE_DRAINING;
+        connection->ns_drain_timeout = n_dhcp4_gettime(CLOCK_BOOTTIME) + UINT64_C(10000000000);
         connection->fd_udp = fd_udp;
         fd_udp = -1;
         connection->client_ip = client->s_addr;
@@ -231,6 +233,7 @@ void n_dhcp4_c_connection_close(NDhcp4CConnection *connection) {
 
         connection->fd_epoll = -1;
         connection->state = N_DHCP4_C_CONNECTION_STATE_CLOSED;
+        connection->ns_drain_timeout = 0;
 }
 
 static int n_dhcp4_c_connection_verify_incoming(NDhcp4CConnection *connection,
@@ -325,7 +328,7 @@ void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection,
         size_t n_send;
 
         if (!connection->request) {
-                *timeoutp = 0;
+                *timeoutp = connection->ns_drain_timeout;
                 return;
         }
 
@@ -369,6 +372,9 @@ void n_dhcp4_c_connection_get_timeout(NDhcp4CConnection *connection,
                 c_assert(0);
         }
 
+        if (connection->ns_drain_timeout != 0 && connection->ns_drain_timeout < timeout)
+            timeout = connection->ns_drain_timeout;
+
         *timeoutp = timeout;
 }
 
@@ -1122,6 +1128,13 @@ int n_dhcp4_c_connection_dispatch_timer(NDhcp4CConnection *connection,
         uint64_t timeout;
         int r;
 
+        if (connection->ns_drain_timeout != 0 && connection->ns_drain_timeout < timestamp) {
+                epoll_ctl(connection->fd_epoll, EPOLL_CTL_DEL, connection->fd_packet, NULL);
+                connection->fd_packet = c_close(connection->fd_packet);
+                connection->state = N_DHCP4_C_CONNECTION_STATE_UDP;
+                connection->ns_drain_timeout = 0;
+        }
+
         if (!connection->request)
                 return 0;
 
@@ -1189,6 +1202,7 @@ int n_dhcp4_c_connection_dispatch_io(NDhcp4CConnection *connection,
                 c_assert(!r);
                 connection->fd_packet = c_close(connection->fd_packet);
                 connection->state = N_DHCP4_C_CONNECTION_STATE_UDP;
+                connection->ns_drain_timeout = 0;
 
                 /* fall-through */
         case N_DHCP4_C_CONNECTION_STATE_UDP:
diff --git a/src/n-dhcp4/src/n-dhcp4-private.h b/src/n-dhcp4/src/n-dhcp4-private.h
index 6b366884..8b17a1a2 100644
--- a/src/n-dhcp4/src/n-dhcp4-private.h
+++ b/src/n-dhcp4/src/n-dhcp4-private.h
@@ -331,6 +331,7 @@ struct NDhcp4CConnection {
 
         NDhcp4Outgoing *request;        /* current request */
 
+        uint64_t ns_drain_timeout;      /* timeout for closing packet socket */
         uint32_t client_ip;             /* client IP address, or 0 */
         uint32_t server_ip;             /* server IP address, or 0 */
         uint16_t mtu;                   /* client mtu, or 0 */
diff --git a/src/nm-cloud-setup/90-nm-cloud-setup.sh b/src/nm-cloud-setup/90-nm-cloud-setup.sh
index 817e2555..ae55b725 100755
--- a/src/nm-cloud-setup/90-nm-cloud-setup.sh
+++ b/src/nm-cloud-setup/90-nm-cloud-setup.sh
@@ -1,9 +1,17 @@
 #!/bin/sh
 
 case "$2" in
-    up|dhcp4-change)
-        if systemctl -q is-enabled nm-cloud-setup.service ; then
-            exec systemctl --no-block restart nm-cloud-setup.service
-        fi
+    pre-up)
+	NO_BLOCK=
         ;;
+    dhcp4-change)
+	NO_BLOCK=--no-block
+        ;;
+    *)
+	exit 0
+	;;
 esac
+
+if systemctl -q is-enabled nm-cloud-setup.service ; then
+    exec systemctl $NO_BLOCK restart nm-cloud-setup.service
+fi
diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c
index 630f26ef..084b5f62 100644
--- a/src/nm-cloud-setup/main.c
+++ b/src/nm-cloud-setup/main.c
@@ -22,10 +22,12 @@ typedef struct {
 } SigTermData;
 
 typedef struct {
+    SigTermData  *sigterm_data;
     GMainLoop    *main_loop;
     GCancellable *cancellable;
     NMCSProvider *provider_result;
     guint         detect_count;
+    gboolean      any_provider_enabled;
 } ProviderDetectData;
 
 static void
@@ -33,38 +35,45 @@ _provider_detect_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
     gs_unref_object NMCSProvider *provider = NMCS_PROVIDER(source);
     gs_free_error GError         *error    = NULL;
-    ProviderDetectData           *dd;
+    ProviderDetectData           *dd       = user_data;
     gboolean                      success;
 
+    nm_assert(dd->detect_count > 0);
+    dd->detect_count--;
+
     success = nmcs_provider_detect_finish(provider, result, &error);
 
     nm_assert(success != (!!error));
 
-    if (nm_utils_error_is_cancelled(error))
-        return;
-
-    dd = user_data;
-
-    nm_assert(dd->detect_count > 0);
-    dd->detect_count--;
-
+    if (nm_utils_error_is_cancelled(error)) {
+        _LOGD("provider %s detection cancelled", nmcs_provider_get_name(provider));
+        goto out;
+    }
     if (error) {
+        if (nm_g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_NOT_READY)) {
+            /* This error tells us, that the provider was not enabled in configuration. */
+        } else
+            dd->any_provider_enabled = TRUE;
         _LOGI("provider %s not detected: %s", nmcs_provider_get_name(provider), error->message);
-        if (dd->detect_count > 0) {
-            /* wait longer. */
-            return;
-        }
-
-        _LOGI("no provider detected");
-        goto done;
+        goto out;
     }
 
     _LOGI("provider %s detected", nmcs_provider_get_name(provider));
     dd->provider_result = g_steal_pointer(&provider);
-
-done:
     g_cancellable_cancel(dd->cancellable);
-    g_main_loop_quit(dd->main_loop);
+
+out:
+    if (dd->detect_count == 0) {
+        if (!dd->provider_result) {
+            NMLogLevel level = LOGL_INFO;
+
+            if (dd->any_provider_enabled && !dd->sigterm_data->signal_received)
+                level = LOGL_WARN;
+
+            _NMLOG(level, "no provider detected");
+        }
+        g_main_loop_quit(dd->main_loop);
+    }
 }
 
 static void
@@ -74,21 +83,21 @@ _provider_detect_sigterm_cb(GCancellable *source, gpointer user_data)
 
     g_cancellable_cancel(dd->cancellable);
     g_clear_object(&dd->provider_result);
-    dd->detect_count = 0;
-    g_main_loop_quit(dd->main_loop);
 }
 
 static NMCSProvider *
-_provider_detect(GCancellable *sigterm_cancellable)
+_provider_detect(SigTermData *sigterm_data)
 {
     nm_auto_unref_gmainloop GMainLoop *main_loop   = g_main_loop_new(NULL, FALSE);
     gs_unref_object GCancellable      *cancellable = g_cancellable_new();
     gs_unref_object NMHttpClient      *http_client = NULL;
     ProviderDetectData                 dd          = {
-                                 .cancellable     = cancellable,
-                                 .main_loop       = main_loop,
-                                 .detect_count    = 0,
-                                 .provider_result = NULL,
+                                 .sigterm_data         = sigterm_data,
+                                 .cancellable          = cancellable,
+                                 .main_loop            = main_loop,
+                                 .detect_count         = 0,
+                                 .provider_result      = NULL,
+                                 .any_provider_enabled = FALSE,
     };
     const GType gtypes[] = {
         NMCS_TYPE_PROVIDER_EC2,
@@ -99,7 +108,7 @@ _provider_detect(GCancellable *sigterm_cancellable)
     int    i;
     gulong cancellable_signal_id;
 
-    cancellable_signal_id = g_cancellable_connect(sigterm_cancellable,
+    cancellable_signal_id = g_cancellable_connect(sigterm_data->cancellable,
                                                   G_CALLBACK(_provider_detect_sigterm_cb),
                                                   &dd,
                                                   NULL);
@@ -123,12 +132,113 @@ _provider_detect(GCancellable *sigterm_cancellable)
         g_main_loop_run(main_loop);
 
 out:
-    nm_clear_g_signal_handler(sigterm_cancellable, &cancellable_signal_id);
+    nm_clear_g_signal_handler(sigterm_data->cancellable, &cancellable_signal_id);
     return dd.provider_result;
 }
 
 /*****************************************************************************/
 
+static NMUtilsNamedValue *
+_map_interfaces_parse(void)
+{
+    gs_free const char **split = NULL;
+    NMUtilsNamedValue   *map_interfaces;
+    const char          *env_var;
+    gsize                i;
+    gsize                j;
+    gsize                alloc_len;
+
+    env_var = g_getenv(NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES);
+
+    if (nm_str_is_empty(env_var))
+        return NULL;
+
+    split = nm_strsplit_set_full(env_var, ";", NM_STRSPLIT_SET_FLAGS_STRSTRIP);
+
+    alloc_len = NM_PTRARRAY_LEN(split) + 1u;
+
+    map_interfaces = g_new(NMUtilsNamedValue, alloc_len);
+
+    _LOGD("test: map interfaces via NM_CLOUD_SETUP_MAP_INTERFACES=\"%s\"", env_var);
+
+    for (i = 0, j = 0; split && split[i]; i++) {
+        NMUtilsNamedValue *m;
+        const char        *str = split[i];
+        char              *hwaddr;
+        const char        *s;
+
+        s = strchr(str, '=');
+        if (!s || str == s)
+            continue;
+
+        hwaddr = nmcs_utils_hwaddr_normalize(&s[1], -1);
+        if (!hwaddr)
+            continue;
+
+        nm_assert(j < alloc_len);
+        m = &map_interfaces[j++];
+
+        *m = (NMUtilsNamedValue){
+            .name      = g_strndup(str, s - str),
+            .value_str = hwaddr,
+        };
+
+        _LOGD("test:  map \"%s\" -> %s", m->name, m->value_str);
+    }
+
+    nm_assert(j < alloc_len);
+    map_interfaces[j++] = (NMUtilsNamedValue){
+        .name      = NULL,
+        .value_str = NULL,
+    };
+
+    return g_steal_pointer(&map_interfaces);
+}
+
+static const char *
+_device_get_hwaddr(NMDeviceEthernet *device)
+{
+    static const NMUtilsNamedValue *gl_map_interfaces_map = NULL;
+    static gsize                    gl_initialized        = 0;
+    const NMUtilsNamedValue        *map                   = NULL;
+
+    nm_assert(NM_IS_DEVICE_ETHERNET(device));
+
+    /* Network interfaces in cloud environments are identified by their permanent
+     * MAC address.
+     *
+     * For testing, we can set NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES
+     * to a ';' separate list of "$INTERFACE=$HWADDR", which means that we
+     * pretend that device with ip-interface "$INTERFACE" has the specified permanent
+     * MAC address. */
+
+    if (g_once_init_enter(&gl_initialized)) {
+        gl_map_interfaces_map = _map_interfaces_parse();
+        g_once_init_leave(&gl_initialized, 1);
+    }
+
+    map = gl_map_interfaces_map;
+    if (G_UNLIKELY(map)) {
+        const char *const iface = nm_device_get_iface(NM_DEVICE(device));
+
+        /* For testing, the device<->hwaddr is remapped and the actual permanent
+         * MAC address of the device ignored. This mapping is configured via
+         * NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES environment variable. */
+
+        if (!iface)
+            return NULL;
+
+        for (; map->name; map++) {
+            if (nm_streq(map->name, iface))
+                return map->value_str;
+        }
+
+        return NULL;
+    }
+
+    return nm_device_ethernet_get_permanent_hw_address(device);
+}
+
 static char **
 _nmc_get_hwaddrs(NMClient *nmc)
 {
@@ -151,7 +261,7 @@ _nmc_get_hwaddrs(NMClient *nmc)
         if (nm_device_get_state(device) < NM_DEVICE_STATE_UNAVAILABLE)
             continue;
 
-        hwaddr = nm_device_ethernet_get_permanent_hw_address(NM_DEVICE_ETHERNET(device));
+        hwaddr = _device_get_hwaddr(NM_DEVICE_ETHERNET(device));
         if (!hwaddr)
             continue;
 
@@ -193,7 +303,7 @@ _nmc_get_device_by_hwaddr(NMClient *nmc, const char *hwaddr)
         if (!NM_IS_DEVICE_ETHERNET(device))
             continue;
 
-        hwaddr_dev = nm_device_ethernet_get_permanent_hw_address(NM_DEVICE_ETHERNET(device));
+        hwaddr_dev = _device_get_hwaddr(NM_DEVICE_ETHERNET(device));
         if (!hwaddr_dev)
             continue;
 
@@ -321,8 +431,9 @@ _nmc_mangle_connection(NMDevice                             *device,
     addrs_new = g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_address_unref);
     rules_new =
         g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_routing_rule_unref);
-    routes_new = g_ptr_array_new_full(config_data->iproutes_len + !!config_data->ipv4s_len,
-                                      (GDestroyNotify) nm_ip_route_unref);
+    routes_new =
+        g_ptr_array_new_full(nm_g_ptr_array_len(config_data->iproutes) + !!config_data->ipv4s_len,
+                             (GDestroyNotify) nm_ip_route_unref);
 
     if (remote_s_ip) {
         guint len;
@@ -428,8 +539,8 @@ _nmc_mangle_connection(NMDevice                             *device,
         }
     }
 
-    for (i = 0; i < config_data->iproutes_len; ++i)
-        g_ptr_array_add(routes_new, config_data->iproutes_arr[i]);
+    for (i = 0; i < nm_g_ptr_array_len(config_data->iproutes); i++)
+        g_ptr_array_add(routes_new, _nm_ip_route_ref(config_data->iproutes->pdata[i]));
 
     addrs_changed = nmcs_setting_ip_replace_ipv4_addresses(s_ip,
                                                            (NMIPAddress **) addrs_new->pdata,
@@ -644,7 +755,7 @@ main(int argc, const char *const *argv)
     gs_free_error GError                                                     *error  = NULL;
     SigTermData                                                               sigterm_data;
 
-    _nm_logging_enabled_init(g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_LOG")));
+    _nm_logging_enabled_init(g_getenv(NMCS_ENV_NM_CLOUD_SETUP_LOG));
 
     _LOGD("nm-cloud-setup %s starting...", NM_DIST_VERSION);
 
@@ -662,7 +773,7 @@ main(int argc, const char *const *argv)
     };
     sigterm_source = nm_g_unix_signal_add_source(SIGTERM, sigterm_handler, &sigterm_data);
 
-    provider = _provider_detect(sigterm_cancellable);
+    provider = _provider_detect(&sigterm_data);
     if (!provider)
         goto done;
 
diff --git a/src/nm-cloud-setup/meson.build b/src/nm-cloud-setup/meson.build
index ea4ad113..b1269ec6 100644
--- a/src/nm-cloud-setup/meson.build
+++ b/src/nm-cloud-setup/meson.build
@@ -16,7 +16,7 @@ if install_systemdunitdir
 
   install_data(
     '90-nm-cloud-setup.sh',
-    install_dir: join_paths(nm_pkglibdir, 'dispatcher.d', 'no-wait.d'),
+    install_dir: join_paths(nm_pkglibdir, 'dispatcher.d', 'pre-up.d'),
   )
 
 endif
diff --git a/src/nm-cloud-setup/nm-cloud-setup-utils.c b/src/nm-cloud-setup/nm-cloud-setup-utils.c
index fb4883b7..75739c7c 100644
--- a/src/nm-cloud-setup/nm-cloud-setup-utils.c
+++ b/src/nm-cloud-setup/nm-cloud-setup-utils.c
@@ -166,258 +166,6 @@ nmcs_wait_for_objects_iterate_until_done(GMainContext *context, int timeout_msec
 
 /*****************************************************************************/
 
-typedef struct {
-    GTask                      *task;
-    GSource                    *source_timeout;
-    GSource                    *source_next_poll;
-    GMainContext               *context;
-    GCancellable               *internal_cancellable;
-    NMCSUtilsPollProbeStartFcn  probe_start_fcn;
-    NMCSUtilsPollProbeFinishFcn probe_finish_fcn;
-    gpointer                    probe_user_data;
-    gulong                      cancellable_id;
-    gint64                      last_poll_start_ms;
-    int                         sleep_timeout_ms;
-    int                         ratelimit_timeout_ms;
-    bool                        completed : 1;
-} PollTaskData;
-
-static void
-_poll_task_data_free(gpointer data)
-{
-    PollTaskData *poll_task_data = data;
-
-    nm_assert(G_IS_TASK(poll_task_data->task));
-    nm_assert(!poll_task_data->source_next_poll);
-    nm_assert(!poll_task_data->source_timeout);
-    nm_assert(poll_task_data->cancellable_id == 0);
-
-    g_main_context_unref(poll_task_data->context);
-
-    nm_g_slice_free(poll_task_data);
-}
-
-static void
-_poll_return(PollTaskData *poll_task_data, GError *error_take)
-{
-    nm_clear_g_source_inst(&poll_task_data->source_next_poll);
-    nm_clear_g_source_inst(&poll_task_data->source_timeout);
-    nm_clear_g_cancellable_disconnect(g_task_get_cancellable(poll_task_data->task),
-                                      &poll_task_data->cancellable_id);
-
-    nm_clear_g_cancellable(&poll_task_data->internal_cancellable);
-
-    if (error_take)
-        g_task_return_error(poll_task_data->task, g_steal_pointer(&error_take));
-    else
-        g_task_return_boolean(poll_task_data->task, TRUE);
-
-    g_object_unref(poll_task_data->task);
-}
-
-static gboolean _poll_start_cb(gpointer user_data);
-
-static void
-_poll_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
-{
-    PollTaskData                     *poll_task_data = user_data;
-    _nm_unused gs_unref_object GTask *task =
-        poll_task_data->task; /* balance ref from _poll_start_cb() */
-    gs_free_error GError *error = NULL;
-    gint64                now_ms;
-    gint64                wait_ms;
-    gboolean              is_finished;
-
-    is_finished =
-        poll_task_data->probe_finish_fcn(source, result, poll_task_data->probe_user_data, &error);
-
-    if (nm_utils_error_is_cancelled(error)) {
-        /* we already handle this differently. Nothing to do. */
-        return;
-    }
-
-    if (error || is_finished) {
-        _poll_return(poll_task_data, g_steal_pointer(&error));
-        return;
-    }
-
-    now_ms = nm_utils_get_monotonic_timestamp_msec();
-    if (poll_task_data->ratelimit_timeout_ms > 0)
-        wait_ms =
-            (poll_task_data->last_poll_start_ms + poll_task_data->ratelimit_timeout_ms) - now_ms;
-    else
-        wait_ms = 0;
-    if (poll_task_data->sleep_timeout_ms > 0)
-        wait_ms = MAX(wait_ms, poll_task_data->sleep_timeout_ms);
-
-    poll_task_data->source_next_poll =
-        nm_g_source_attach(nm_g_timeout_source_new(MAX(1, wait_ms),
-                                                   G_PRIORITY_DEFAULT,
-                                                   _poll_start_cb,
-                                                   poll_task_data,
-                                                   NULL),
-                           poll_task_data->context);
-}
-
-static gboolean
-_poll_start_cb(gpointer user_data)
-{
-    PollTaskData *poll_task_data = user_data;
-
-    nm_clear_g_source_inst(&poll_task_data->source_next_poll);
-
-    poll_task_data->last_poll_start_ms = nm_utils_get_monotonic_timestamp_msec();
-
-    g_object_ref(poll_task_data->task); /* balanced by _poll_done_cb() */
-
-    poll_task_data->probe_start_fcn(poll_task_data->internal_cancellable,
-                                    poll_task_data->probe_user_data,
-                                    _poll_done_cb,
-                                    poll_task_data);
-
-    return G_SOURCE_CONTINUE;
-}
-
-static gboolean
-_poll_timeout_cb(gpointer user_data)
-{
-    PollTaskData *poll_task_data = user_data;
-
-    _poll_return(poll_task_data, nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "timeout expired"));
-    return G_SOURCE_CONTINUE;
-}
-
-static void
-_poll_cancelled_cb(GObject *object, gpointer user_data)
-{
-    PollTaskData *poll_task_data = user_data;
-    GError       *error          = NULL;
-
-    nm_clear_g_signal_handler(g_task_get_cancellable(poll_task_data->task),
-                              &poll_task_data->cancellable_id);
-    nm_utils_error_set_cancelled(&error, FALSE, NULL);
-    _poll_return(poll_task_data, error);
-}
-
-/**
- * nmcs_utils_poll:
- * @poll_timeout_ms: if >= 0, then this is the overall timeout for how long we poll.
- *   When this timeout expires, the request completes with failure (and error set).
- * @ratelimit_timeout_ms: if > 0, we ratelimit the starts from one prope_start_fcn
- *   call to the next.
- * @sleep_timeout_ms: if > 0, then we wait after a probe finished this timeout
- *   before the next. Together with @ratelimit_timeout_ms this determines how
- *   frequently we probe.
- * @probe_start_fcn: used to start a (asynchronous) probe. A probe must be completed
- *   by calling the provided callback. While a probe is in progress, we will not
- *   start another. This function is already invoked the first time synchronously,
- *   during nmcs_utils_poll().
- * @probe_finish_fcn: will be called from the callback of @probe_start_fcn. If the
- *   function returns %TRUE (polling done) or an error, polling stops. Otherwise,
- *   another poll will be started.
- * @probe_user_data: user_data for the probe functions.
- * @cancellable: cancellable for polling.
- * @callback: when polling completes.
- * @user_data: for @callback.
- *
- * This uses the current g_main_context_get_thread_default() for scheduling
- * actions.
- */
-void
-nmcs_utils_poll(int                         poll_timeout_ms,
-                int                         ratelimit_timeout_ms,
-                int                         sleep_timeout_ms,
-                NMCSUtilsPollProbeStartFcn  probe_start_fcn,
-                NMCSUtilsPollProbeFinishFcn probe_finish_fcn,
-                gpointer                    probe_user_data,
-                GCancellable               *cancellable,
-                GAsyncReadyCallback         callback,
-                gpointer                    user_data)
-{
-    PollTaskData *poll_task_data;
-
-    poll_task_data  = g_slice_new(PollTaskData);
-    *poll_task_data = (PollTaskData){
-        .task             = nm_g_task_new(NULL, cancellable, nmcs_utils_poll, callback, user_data),
-        .probe_start_fcn  = probe_start_fcn,
-        .probe_finish_fcn = probe_finish_fcn,
-        .probe_user_data  = probe_user_data,
-        .completed        = FALSE,
-        .context          = g_main_context_ref_thread_default(),
-        .sleep_timeout_ms = sleep_timeout_ms,
-        .ratelimit_timeout_ms = ratelimit_timeout_ms,
-        .internal_cancellable = g_cancellable_new(),
-    };
-
-    nmcs_wait_for_objects_register(poll_task_data->task);
-
-    g_task_set_task_data(poll_task_data->task, poll_task_data, _poll_task_data_free);
-
-    if (poll_timeout_ms >= 0) {
-        poll_task_data->source_timeout =
-            nm_g_source_attach(nm_g_timeout_source_new(poll_timeout_ms,
-                                                       G_PRIORITY_DEFAULT,
-                                                       _poll_timeout_cb,
-                                                       poll_task_data,
-                                                       NULL),
-                               poll_task_data->context);
-    }
-
-    poll_task_data->source_next_poll = nm_g_source_attach(
-        nm_g_idle_source_new(G_PRIORITY_DEFAULT_IDLE, _poll_start_cb, poll_task_data, NULL),
-        poll_task_data->context);
-
-    if (cancellable) {
-        gulong signal_id;
-
-        signal_id = g_cancellable_connect(cancellable,
-                                          G_CALLBACK(_poll_cancelled_cb),
-                                          poll_task_data,
-                                          NULL);
-        if (signal_id == 0) {
-            /* the request is already cancelled. Return. */
-            return;
-        }
-        poll_task_data->cancellable_id = signal_id;
-    }
-}
-
-/**
- * nmcs_utils_poll_finish:
- * @result: the GAsyncResult from the GAsyncReadyCallback callback.
- * @probe_user_data: the user data provided to nmcs_utils_poll().
- * @error: the failure code.
- *
- * Returns: %TRUE if the polling completed with success. In that case,
- *   the error won't be set.
- *   If the request was cancelled, this is indicated by @error and
- *   %FALSE will be returned.
- *   If the probe returned a failure, this returns %FALSE and the error
- *   provided by @probe_finish_fcn.
- *   If the request times out, this returns %FALSE with error set.
- *   Error is always set if (and only if) the function returns %FALSE.
- */
-gboolean
-nmcs_utils_poll_finish(GAsyncResult *result, gpointer *probe_user_data, GError **error)
-{
-    GTask        *task;
-    PollTaskData *poll_task_data;
-
-    g_return_val_if_fail(nm_g_task_is_valid(result, NULL, nmcs_utils_poll), FALSE);
-    g_return_val_if_fail(!error || !*error, FALSE);
-
-    task = G_TASK(result);
-
-    if (probe_user_data) {
-        poll_task_data = g_task_get_task_data(task);
-        NM_SET_OUT(probe_user_data, poll_task_data->probe_user_data);
-    }
-
-    return g_task_propagate_boolean(task, error);
-}
-
-/*****************************************************************************/
-
 char *
 nmcs_utils_hwaddr_normalize(const char *hwaddr, gssize len)
 {
diff --git a/src/nm-cloud-setup/nm-cloud-setup-utils.h b/src/nm-cloud-setup/nm-cloud-setup-utils.h
index fed0f4b6..4ca4634c 100644
--- a/src/nm-cloud-setup/nm-cloud-setup-utils.h
+++ b/src/nm-cloud-setup/nm-cloud-setup-utils.h
@@ -7,9 +7,20 @@
 
 /*****************************************************************************/
 
-/* mark names for  variables that can be used as configuration. Search
- * for NMCS_ENV_VARIABLE() to find all honored environment variables. */
-#define NMCS_ENV_VARIABLE(var) "" var ""
+/* Environment variables for configuring nm-cloud-setup */
+#define NMCS_ENV_NM_CLOUD_SETUP_ALIYUN "NM_CLOUD_SETUP_ALIYUN"
+#define NMCS_ENV_NM_CLOUD_SETUP_AZURE  "NM_CLOUD_SETUP_AZURE"
+#define NMCS_ENV_NM_CLOUD_SETUP_EC2    "NM_CLOUD_SETUP_EC2"
+#define NMCS_ENV_NM_CLOUD_SETUP_GCP    "NM_CLOUD_SETUP_GCP"
+#define NMCS_ENV_NM_CLOUD_SETUP_LOG    "NM_CLOUD_SETUP_LOG"
+
+/* Undocumented/internal environment variables for configuring nm-cloud-setup.
+ * These are mainly for testing/debugging. */
+#define NMCS_ENV_NM_CLOUD_SETUP_ALIYUN_HOST    "NM_CLOUD_SETUP_ALIYUN_HOST"
+#define NMCS_ENV_NM_CLOUD_SETUP_AZURE_HOST     "NM_CLOUD_SETUP_AZURE_HOST"
+#define NMCS_ENV_NM_CLOUD_SETUP_EC2_HOST       "NM_CLOUD_SETUP_EC2_HOST"
+#define NMCS_ENV_NM_CLOUD_SETUP_GCP_HOST       "NM_CLOUD_SETUP_GCP_HOST"
+#define NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES "NM_CLOUD_SETUP_MAP_INTERFACES"
 
 /*****************************************************************************/
 
@@ -40,30 +51,6 @@ gboolean nmcs_wait_for_objects_iterate_until_done(GMainContext *context, int tim
 
 /*****************************************************************************/
 
-typedef void (*NMCSUtilsPollProbeStartFcn)(GCancellable       *cancellable,
-                                           gpointer            probe_user_data,
-                                           GAsyncReadyCallback callback,
-                                           gpointer            user_data);
-
-typedef gboolean (*NMCSUtilsPollProbeFinishFcn)(GObject      *source,
-                                                GAsyncResult *result,
-                                                gpointer      probe_user_data,
-                                                GError      **error);
-
-void nmcs_utils_poll(int                         poll_timeout_ms,
-                     int                         ratelimit_timeout_ms,
-                     int                         sleep_timeout_ms,
-                     NMCSUtilsPollProbeStartFcn  probe_start_fcn,
-                     NMCSUtilsPollProbeFinishFcn probe_finish_fcn,
-                     gpointer                    probe_user_data,
-                     GCancellable               *cancellable,
-                     GAsyncReadyCallback         callback,
-                     gpointer                    user_data);
-
-gboolean nmcs_utils_poll_finish(GAsyncResult *result, gpointer *probe_user_data, GError **error);
-
-/*****************************************************************************/
-
 char *nmcs_utils_hwaddr_normalize(const char *hwaddr, gssize len);
 
 static inline char *
@@ -104,6 +91,38 @@ const char *nmcs_utils_parse_get_full_line(GBytes *mem, const char *needle);
 
 /*****************************************************************************/
 
+#define NMCS_DEFINE_HOST_BASE(base_fcn, nmcs_env_host, default_host)               \
+    static const char *base_fcn(void)                                              \
+    {                                                                              \
+        static const char *base_cached = NULL;                                     \
+        const char        *base;                                                   \
+                                                                                   \
+again:                                                                             \
+        base = g_atomic_pointer_get(&base_cached);                                 \
+        if (G_UNLIKELY(!base)) {                                                   \
+            /* The base URI can be set via environment variable. \
+             * This is mainly for testing, it's not usually supposed to be configured. \
+             * Consider this private API! */                 \
+            base = g_getenv("" nmcs_env_host "");                                  \
+            base = nmcs_utils_uri_complete_interned(base) ?: ("" default_host ""); \
+                                                                                   \
+            if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base))  \
+                goto again;                                                        \
+                                                                                   \
+            if (!nm_streq(base, ("" default_host ""))) {                           \
+                _LOGD("test: mock %s=\"%s\" (default \"%s\")",                     \
+                      "" nmcs_env_host "",                                         \
+                      base,                                                        \
+                      "" default_host "");                                         \
+            }                                                                      \
+        }                                                                          \
+                                                                                   \
+        return base;                                                               \
+    }                                                                              \
+    _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
+
+/*****************************************************************************/
+
 char *nmcs_utils_uri_build_concat_v(const char *base, const char **components, gsize n_components);
 
 #define nmcs_utils_uri_build_concat(base, ...) \
diff --git a/src/nm-cloud-setup/nm-cloud-setup.service.in b/src/nm-cloud-setup/nm-cloud-setup.service.in
index f4b0e263..a2b4581f 100644
--- a/src/nm-cloud-setup/nm-cloud-setup.service.in
+++ b/src/nm-cloud-setup/nm-cloud-setup.service.in
@@ -1,6 +1,7 @@
 [Unit]
 Description=Automatically configure NetworkManager in cloud
 Documentation=man:nm-cloud-setup(8)
+Before=network-online.target
 After=NetworkManager.service
 
 [Service]
diff --git a/src/nm-cloud-setup/nm-http-client.c b/src/nm-cloud-setup/nm-http-client.c
index db123f10..20ef6473 100644
--- a/src/nm-cloud-setup/nm-http-client.c
+++ b/src/nm-cloud-setup/nm-http-client.c
@@ -359,8 +359,8 @@ nm_http_client_req(NMHttpClient       *self,
  * nm_http_client_req_finish:
  * @self: the #NMHttpClient instance
  * @result: the #GAsyncResult which to complete.
- * @out_response_code: (allow-none) (out): the HTTP response code or -1 on other error.
- * @out_response_data: (allow-none) (transfer full): the HTTP response data, if any.
+ * @out_response_code: (out) (optional): the HTTP response code or -1 on other error.
+ * @out_response_data: (out) (optional) (nullable) (transfer full): the HTTP response data, if any.
  *   The GBytes buffer is guaranteed to have a trailing NUL character *after* the
  *   returned buffer size. That means, you can always trust that the buffer is NUL terminated
  *   and that there is one additional hidden byte after the data.
@@ -430,6 +430,12 @@ _poll_req_data_free(gpointer data)
 }
 
 static void
+_poll_reg_probe_register_object_fcn(GObject *object, gpointer user_data)
+{
+    nmcs_wait_for_objects_register(object);
+}
+
+static void
 _poll_req_probe_start_fcn(GCancellable       *cancellable,
                           gpointer            probe_user_data,
                           GAsyncReadyCallback callback,
@@ -508,13 +514,14 @@ _poll_req_probe_finish_fcn(GObject      *source,
 static void
 _poll_req_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    PollReqData          *poll_req_data = user_data;
+    PollReqData          *poll_req_data = NULL;
     gs_free_error GError *error         = NULL;
     gboolean              success;
 
-    success = nmcs_utils_poll_finish(result, NULL, &error);
+    success = nm_utils_poll_finish(result, (gpointer *) &poll_req_data, &error);
 
     nm_assert((!!success) == (!error));
+    nm_assert(poll_req_data);
 
     if (error)
         g_task_return_error(poll_req_data->task, g_steal_pointer(&error));
@@ -575,15 +582,16 @@ nm_http_client_poll_req(NMHttpClient               *self,
     context =
         nm_g_main_context_push_thread_default_if_necessary(nm_http_client_get_main_context(self));
 
-    nmcs_utils_poll(poll_timeout_ms,
-                    ratelimit_timeout_ms,
-                    0,
-                    _poll_req_probe_start_fcn,
-                    _poll_req_probe_finish_fcn,
-                    poll_req_data,
-                    cancellable,
-                    _poll_req_done_cb,
-                    poll_req_data);
+    nm_utils_poll(poll_timeout_ms,
+                  ratelimit_timeout_ms,
+                  0,
+                  _poll_reg_probe_register_object_fcn,
+                  _poll_req_probe_start_fcn,
+                  _poll_req_probe_finish_fcn,
+                  poll_req_data,
+                  cancellable,
+                  _poll_req_done_cb,
+                  NULL);
 }
 
 gboolean
diff --git a/src/nm-cloud-setup/nmcs-provider-aliyun.c b/src/nm-cloud-setup/nmcs-provider-aliyun.c
index 7f51f431..20010a2c 100644
--- a/src/nm-cloud-setup/nmcs-provider-aliyun.c
+++ b/src/nm-cloud-setup/nmcs-provider-aliyun.c
@@ -18,27 +18,7 @@
 #define NM_ALIYUN_METADATA_URL_BASE /* $NM_ALIYUN_BASE/$NM_ALIYUN_API_VERSION */ \
     "/meta-data/network/interfaces/macs/"
 
-static const char *
-_aliyun_base(void)
-{
-    static const char *base_cached = NULL;
-    const char        *base;
-
-again:
-    base = g_atomic_pointer_get(&base_cached);
-    if (G_UNLIKELY(!base)) {
-        /* The base URI can be set via environment variable.
-         * This is mainly for testing, it's not usually supposed to be configured.
-         * Consider this private API! */
-        base = g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_ALIYUN_HOST"));
-        base = nmcs_utils_uri_complete_interned(base) ?: ("" NM_ALIYUN_HOST);
-
-        if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base))
-            goto again;
-    }
-
-    return base;
-}
+NMCS_DEFINE_HOST_BASE(_aliyun_base, NMCS_ENV_NM_CLOUD_SETUP_ALIYUN_HOST, NM_ALIYUN_HOST);
 
 #define _aliyun_uri_concat(...) nmcs_utils_uri_build_concat(_aliyun_base(), __VA_ARGS__)
 #define _aliyun_uri_interfaces(...) \
@@ -558,7 +538,7 @@ nmcs_provider_aliyun_class_init(NMCSProviderAliyunClass *klass)
     NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
 
     provider_class->_name                 = "aliyun";
-    provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_ALIYUN");
+    provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_ALIYUN;
     provider_class->detect                = detect;
     provider_class->get_config            = get_config;
 }
diff --git a/src/nm-cloud-setup/nmcs-provider-azure.c b/src/nm-cloud-setup/nmcs-provider-azure.c
index 69946f5c..771c43d9 100644
--- a/src/nm-cloud-setup/nmcs-provider-azure.c
+++ b/src/nm-cloud-setup/nmcs-provider-azure.c
@@ -17,8 +17,10 @@
 #define NM_AZURE_METADATA_URL_BASE /* $NM_AZURE_BASE/$NM_AZURE_API_VERSION */ \
     "/metadata/instance/network/interface/"
 
+NMCS_DEFINE_HOST_BASE(_azure_base, NMCS_ENV_NM_CLOUD_SETUP_AZURE_HOST, NM_AZURE_BASE);
+
 #define _azure_uri_concat(...) \
-    nmcs_utils_uri_build_concat(NM_AZURE_BASE, __VA_ARGS__, NM_AZURE_API_VERSION)
+    nmcs_utils_uri_build_concat(_azure_base(), __VA_ARGS__, NM_AZURE_API_VERSION)
 #define _azure_uri_interfaces(...) _azure_uri_concat(NM_AZURE_METADATA_URL_BASE, ##__VA_ARGS__)
 
 /*****************************************************************************/
@@ -564,7 +566,7 @@ nmcs_provider_azure_class_init(NMCSProviderAzureClass *klass)
     NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
 
     provider_class->_name                 = "azure";
-    provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_AZURE");
+    provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_AZURE;
     provider_class->detect                = detect;
     provider_class->get_config            = get_config;
 }
diff --git a/src/nm-cloud-setup/nmcs-provider-ec2.c b/src/nm-cloud-setup/nmcs-provider-ec2.c
index 65a8f629..9cdc7a0c 100644
--- a/src/nm-cloud-setup/nmcs-provider-ec2.c
+++ b/src/nm-cloud-setup/nmcs-provider-ec2.c
@@ -21,27 +21,7 @@
 #define NM_EC2_TOKEN_TTL_HEADER "X-aws-ec2-metadata-token-ttl-seconds: 180"
 #define NM_EC2_TOKEN_HEADER     "X-aws-ec2-metadata-token: "
 
-static const char *
-_ec2_base(void)
-{
-    static const char *base_cached = NULL;
-    const char        *base;
-
-again:
-    base = g_atomic_pointer_get(&base_cached);
-    if (G_UNLIKELY(!base)) {
-        /* The base URI can be set via environment variable.
-         * This is mainly for testing, it's not usually supposed to be configured.
-         * Consider this private API! */
-        base = g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_EC2_HOST"));
-        base = nmcs_utils_uri_complete_interned(base) ?: ("" NM_EC2_BASE);
-
-        if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base))
-            goto again;
-    }
-
-    return base;
-}
+NMCS_DEFINE_HOST_BASE(_ec2_base, NMCS_ENV_NM_CLOUD_SETUP_EC2_HOST, NM_EC2_BASE);
 
 #define _ec2_uri_concat(...) nmcs_utils_uri_build_concat(_ec2_base(), __VA_ARGS__)
 #define _ec2_uri_interfaces(...) \
@@ -435,7 +415,7 @@ nmcs_provider_ec2_class_init(NMCSProviderEC2Class *klass)
     object_class->dispose = dispose;
 
     provider_class->_name                 = "ec2";
-    provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_EC2");
+    provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_EC2;
     provider_class->detect                = detect;
     provider_class->get_config            = get_config;
 }
diff --git a/src/nm-cloud-setup/nmcs-provider-gcp.c b/src/nm-cloud-setup/nmcs-provider-gcp.c
index ca354865..4d9ef965 100644
--- a/src/nm-cloud-setup/nmcs-provider-gcp.c
+++ b/src/nm-cloud-setup/nmcs-provider-gcp.c
@@ -13,15 +13,19 @@
 #define HTTP_POLL_TIMEOUT_MS 10000
 #define HTTP_RATE_LIMIT_MS   1000
 
-#define NM_GCP_HOST              "metadata.google.internal"
-#define NM_GCP_BASE              "http://" NM_GCP_HOST
-#define NM_GCP_API_VERSION       "/v1"
-#define NM_GCP_METADATA_URL_BASE NM_GCP_BASE "/computeMetadata" NM_GCP_API_VERSION "/instance"
-#define NM_GCP_METADATA_URL_NET  "/network-interfaces/"
+#define NM_GCP_HOST             "metadata.google.internal"
+#define NM_GCP_BASE             "http://" NM_GCP_HOST
+#define NM_GCP_API_VERSION      "/v1"
+#define NM_GCP_METADATA_URL_NET "/network-interfaces/"
 
 #define NM_GCP_METADATA_HEADER "Metadata-Flavor: Google"
 
-#define _gcp_uri_concat(...)     nmcs_utils_uri_build_concat(NM_GCP_METADATA_URL_BASE, __VA_ARGS__)
+NMCS_DEFINE_HOST_BASE(_gcp_base, NMCS_ENV_NM_CLOUD_SETUP_GCP_HOST, NM_GCP_BASE);
+
+#define _gcp_uri_concat(...)                                                       \
+    nmcs_utils_uri_build_concat(_gcp_base(),                                       \
+                                "/computeMetadata" NM_GCP_API_VERSION "/instance", \
+                                __VA_ARGS__)
 #define _gcp_uri_interfaces(...) _gcp_uri_concat(NM_GCP_METADATA_URL_NET, ##__VA_ARGS__)
 
 /*****************************************************************************/
@@ -73,7 +77,7 @@ detect(NMCSProvider *provider, GTask *task)
     http_client = nmcs_provider_get_http_client(provider);
 
     nm_http_client_poll_req(http_client,
-                            (uri = _gcp_uri_concat("id")),
+                            (uri = _gcp_uri_concat("/id")),
                             HTTP_TIMEOUT_MS,
                             256 * 1024,
                             7000,
@@ -112,7 +116,6 @@ _get_config_fip_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     GCPIfaceData                   *iface_data = user_data;
     gs_free_error GError           *error      = NULL;
     gs_free char                   *ipaddr     = NULL;
-    NMIPRoute                     **routes_arr;
     NMIPRoute                      *route_new;
 
     nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
@@ -137,15 +140,14 @@ _get_config_fip_cb(GObject *source, GAsyncResult *result, gpointer user_data)
           ipaddr);
 
     iface_get_config = iface_data->iface_get_config;
-    routes_arr       = iface_get_config->iproutes_arr;
 
     route_new = nm_ip_route_new(AF_INET, ipaddr, 32, NULL, 100, &error);
     if (error)
         goto out_done;
 
     nm_ip_route_set_attribute(route_new, NM_IP_ROUTE_ATTRIBUTE_TYPE, g_variant_new_string("local"));
-    routes_arr[iface_get_config->iproutes_len] = route_new;
-    ++iface_get_config->iproutes_len;
+
+    g_ptr_array_add(iface_get_config->iproutes, route_new);
 
 out_done:
     if (!error) {
@@ -215,7 +217,8 @@ _get_config_ips_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
         goto out_error;
     }
 
-    iface_data->iface_get_config->iproutes_arr = g_new(NMIPRoute *, iface_data->n_fips_pending);
+    iface_data->iface_get_config->iproutes =
+        g_ptr_array_new_full(iface_data->n_fips_pending, (GDestroyNotify) nm_ip_route_unref);
 
     for (i = 0; i < uri_arr->len; ++i) {
         const char         *str = uri_arr->pdata[i];
@@ -459,7 +462,7 @@ nmcs_provider_gcp_class_init(NMCSProviderGCPClass *klass)
     NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
 
     provider_class->_name                 = "GCP";
-    provider_class->_env_provider_enabled = NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_GCP");
+    provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_GCP;
     provider_class->detect                = detect;
     provider_class->get_config            = get_config;
 }
diff --git a/src/nm-cloud-setup/nmcs-provider.c b/src/nm-cloud-setup/nmcs-provider.c
index fd9a61b8..5b4412b3 100644
--- a/src/nm-cloud-setup/nmcs-provider.c
+++ b/src/nm-cloud-setup/nmcs-provider.c
@@ -155,7 +155,7 @@ nmcs_provider_detect(NMCSProvider       *self,
     env = g_getenv(NMCS_PROVIDER_GET_CLASS(self)->_env_provider_enabled);
     if (!_nm_utils_ascii_str_to_bool(env, FALSE)) {
         g_task_return_error(task,
-                            nm_utils_error_new(NM_UTILS_ERROR_UNKNOWN, "provider is disabled"));
+                            nm_utils_error_new(NM_UTILS_ERROR_NOT_READY, "provider is disabled"));
         return;
     }
 
@@ -216,7 +216,7 @@ _iface_data_free(gpointer data)
     NMCSProviderGetConfigIfaceData *iface_data = data;
 
     g_free(iface_data->ipv4s_arr);
-    g_free(iface_data->iproutes_arr);
+    nm_g_ptr_array_unref(iface_data->iproutes);
     g_free((char *) iface_data->hwaddr);
 
     nm_g_slice_free(iface_data);
diff --git a/src/nm-cloud-setup/nmcs-provider.h b/src/nm-cloud-setup/nmcs-provider.h
index 09cdb414..9e5eeebe 100644
--- a/src/nm-cloud-setup/nmcs-provider.h
+++ b/src/nm-cloud-setup/nmcs-provider.h
@@ -34,8 +34,8 @@ typedef struct {
     bool      has_cidr : 1;
     bool      has_gateway : 1;
 
-    NMIPRoute **iproutes_arr;
-    gsize       iproutes_len;
+    /* Array of NMIPRoute (must own/free the entries). */
+    GPtrArray *iproutes;
 
     /* TRUE, if the configuration was requested via hwaddrs argument to
      * nmcs_provider_get_config(). */
@@ -59,7 +59,8 @@ static inline gboolean
 nmcs_provider_get_config_iface_data_is_valid(const NMCSProviderGetConfigIfaceData *config_data)
 {
     return config_data && config_data->iface_idx >= 0
-           && ((config_data->has_ipv4s && config_data->has_cidr) || config_data->iproutes_len);
+           && ((config_data->has_ipv4s && config_data->has_cidr)
+               || nm_g_ptr_array_len(config_data->iproutes) > 0);
 }
 
 /*****************************************************************************/
diff --git a/src/nm-dispatcher/nm-dispatcher.c b/src/nm-dispatcher/nm-dispatcher.c
index 80fe40a0..97b85813 100644
--- a/src/nm-dispatcher/nm-dispatcher.c
+++ b/src/nm-dispatcher/nm-dispatcher.c
@@ -243,7 +243,7 @@ _idle_timeout_restart(void)
 /**
  * next_request:
  *
- * @request: (allow-none): the request to set as next. If %NULL, dequeue the next
+ * @request: (nullable): the request to set as next. If %NULL, dequeue the next
  * waiting request. Otherwise, try to set the given request.
  *
  * Sets the currently active request (@current_request). The current request
diff --git a/src/nm-initrd-generator/nmi-cmdline-reader.c b/src/nm-initrd-generator/nmi-cmdline-reader.c
index bafad041..68fe7004 100644
--- a/src/nm-initrd-generator/nmi-cmdline-reader.c
+++ b/src/nm-initrd-generator/nmi-cmdline-reader.c
@@ -1274,7 +1274,39 @@ reader_parse_ethtool(Reader *reader, char *argument)
 static void
 _normalize_conn(gpointer key, gpointer value, gpointer user_data)
 {
-    NMConnection *connection = value;
+    NMConnection      *connection = value;
+    NMSettingIPConfig *s_ip4 = NULL, *s_ip6 = NULL;
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    if (s_ip4) {
+        const char *method = nm_setting_ip_config_get_method(s_ip4);
+
+        if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+            g_object_set(s_ip4,
+                         NM_SETTING_IP_CONFIG_DHCP_HOSTNAME,
+                         NULL,
+                         NM_SETTING_IP_CONFIG_DHCP_TIMEOUT,
+                         NULL,
+                         NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER,
+                         NULL,
+                         NULL);
+        }
+    }
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    if (s_ip6) {
+        const char *method = nm_setting_ip_config_get_method(s_ip6);
+
+        if (!nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+            && !nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+            g_object_set(s_ip6,
+                         NM_SETTING_IP_CONFIG_DHCP_HOSTNAME,
+                         NULL,
+                         NM_SETTING_IP_CONFIG_DHCP_TIMEOUT,
+                         NULL,
+                         NULL);
+        }
+    }
 
     nm_connection_normalize(connection, NULL, NULL, NULL);
 }
diff --git a/src/nm-initrd-generator/nmi-dt-reader.c b/src/nm-initrd-generator/nmi-dt-reader.c
index f1279d58..b273822a 100644
--- a/src/nm-initrd-generator/nmi-dt-reader.c
+++ b/src/nm-initrd-generator/nmi-dt-reader.c
@@ -96,6 +96,11 @@ str_addr(const char *str, int *family)
 {
     NMIPAddr addr_bin;
 
+    /* For IPv4, we need to be more tolerant than inet_pton() to recognize
+     * things like the extra zeroes in "255.255.255.000".
+     *
+     * Pass accept_legacy=TRUE to nm_inet_parse_bin_full(), which also accepts
+     * such forms (but not everything which inet_aton() accepts). */
     if (!nm_inet_parse_bin_full(*family, TRUE, str, family, &addr_bin)) {
         _LOGW(LOGD_CORE, "Malformed IP address: '%s'", str);
         return NULL;
diff --git a/src/nm-initrd-generator/tests/test-cmdline-reader.c b/src/nm-initrd-generator/tests/test-cmdline-reader.c
index 2dfc23a3..7124d74e 100644
--- a/src/nm-initrd-generator/tests/test-cmdline-reader.c
+++ b/src/nm-initrd-generator/tests/test-cmdline-reader.c
@@ -399,6 +399,7 @@ test_if_auto_with_mtu_and_mac(void)
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, NULL);
     g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
 
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
@@ -451,7 +452,7 @@ test_if_ip4_manual(void)
     g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "192.0.2.2");
     g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 24);
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.0.2.1");
-    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "hostname0.example.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, NULL);
     g_assert_cmpint(nm_setting_ip_config_get_required_timeout(s_ip4), ==, -1);
 
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
@@ -459,6 +460,7 @@ test_if_ip4_manual(void)
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
                     ==,
                     NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, NULL);
     g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
     g_assert_cmpint(nm_setting_ip_config_get_required_timeout(s_ip6), ==, -1);
 
@@ -480,12 +482,65 @@ test_if_ip4_manual(void)
     g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "203.0.113.2");
     g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 26);
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "203.0.113.1");
-    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "hostname1.example.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, NULL);
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    g_assert(s_ip6);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
+}
+
+static void
+test_if_ip4_auto(void)
+{
+    gs_unref_hashtable GHashTable *connections = NULL;
+    const char *const             *ARGV        = NM_MAKE_STRV("ip=172.25.1.1::172.25.1.2:24:"
+                                                              "myhostname:eth0:dhcp",
+                                           "rd.net.timeout.dhcp=10");
+    NMConnection                  *connection;
+    NMSettingConnection           *s_con;
+    NMSettingIPConfig             *s_ip4;
+    NMSettingIPConfig             *s_ip6;
+    NMIPAddress                   *ip_addr;
+    gs_free char                  *hostname            = NULL;
+    gint64                         carrier_timeout_sec = 0;
+
+    connections = _parse(ARGV, &hostname, &carrier_timeout_sec);
+    g_assert_cmpint(g_hash_table_size(connections), ==, 1);
+    g_assert_cmpstr(hostname, ==, "myhostname");
+    g_assert_cmpint(carrier_timeout_sec, ==, 0);
+
+    connection = g_hash_table_lookup(connections, "eth0");
+    nmtst_assert_connection_verifies_without_normalization(connection);
+    g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth0");
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_assert(s_con);
+    g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, -1);
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+    g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0);
+    g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 0);
+    g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
+    g_assert_cmpint(nm_setting_ip_config_get_dhcp_timeout(s_ip4), ==, 10);
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "172.25.1.2");
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "myhostname");
+
+    ip_addr = nm_setting_ip_config_get_address(s_ip4, 0);
+    g_assert(ip_addr);
+    g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "172.25.1.1");
+    g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 24);
 
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
     g_assert(s_ip6);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "myhostname");
     g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
+    g_assert_cmpint(nm_setting_ip_config_get_dhcp_timeout(s_ip6), ==, 10);
+    g_assert_cmpint(nm_setting_ip_config_get_required_timeout(s_ip6), ==, -1);
 }
 
 static void
@@ -535,6 +590,7 @@ test_if_ip6_manual(void)
     const char *const             *ARGV = NM_MAKE_STRV("ip=[2001:0db8::02]/64::[2001:0db8::01]::"
                                                        "hostname0.example.com:eth4::[2001:0db8::53]");
     NMConnection                  *connection;
+    NMSettingIPConfig             *s_ip4;
     NMSettingIPConfig             *s_ip6;
     NMIPAddress                   *ip_addr;
     gs_free char                  *hostname            = NULL;
@@ -549,6 +605,11 @@ test_if_ip6_manual(void)
     nmtst_assert_connection_verifies_without_normalization(connection);
     g_assert_cmpstr(nm_connection_get_id(connection), ==, "eth4");
 
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "hostname0.example.com");
+
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
     g_assert(s_ip6);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
@@ -564,7 +625,7 @@ test_if_ip6_manual(void)
     g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "2001:db8::2");
     g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 64);
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2001:db8::1");
-    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "hostname0.example.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, NULL);
 }
 
 static void
@@ -616,6 +677,7 @@ test_if_mac_ifname(void)
     const char *const             *ARGV = NM_MAKE_STRV("ip=[2001:0db8::42]/64::[2001:0db8::01]::"
                                                        "hostname0:00-11-22-33-44-55::[2001:0db8::53]");
     NMConnection                  *connection;
+    NMSettingIPConfig             *s_ip4;
     NMSettingIPConfig             *s_ip6;
     NMSettingWired                *s_wired;
     NMIPAddress                   *ip_addr;
@@ -636,6 +698,11 @@ test_if_mac_ifname(void)
     g_assert(s_wired);
     g_assert_cmpstr(nm_setting_wired_get_mac_address(s_wired), ==, "00:11:22:33:44:55");
 
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    g_assert(s_ip4);
+    g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "hostname0");
+
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
     g_assert(s_ip6);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
@@ -646,12 +713,13 @@ test_if_mac_ifname(void)
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip6, 0), ==, "2001:db8::53");
     g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip6), ==, 0);
     g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip6), ==, 1);
+    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2001:db8::1");
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, NULL);
+
     ip_addr = nm_setting_ip_config_get_address(s_ip6, 0);
     g_assert(ip_addr);
     g_assert_cmpstr(nm_ip_address_get_address(ip_addr), ==, "2001:db8::42");
     g_assert_cmpint(nm_ip_address_get_prefix(ip_addr), ==, 64);
-    g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, "2001:db8::1");
-    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "hostname0");
 }
 
 static void
@@ -1040,6 +1108,7 @@ test_bond_ip(void)
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
                     ==,
                     NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, NULL);
     g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip6));
     g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip6), ==, 0);
     g_assert(!nm_setting_ip_config_get_gateway(s_ip6));
@@ -1403,6 +1472,7 @@ test_team(void)
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
+    g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, NULL);
     g_assert(!nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
     g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0);
     g_assert(!nm_setting_ip_config_get_gateway(s_ip4));
@@ -2551,6 +2621,7 @@ main(int argc, char **argv)
     g_test_add_func("/initrd/cmdline/if_dhcp6", test_if_dhcp6);
     g_test_add_func("/initrd/cmdline/if_auto_with_mtu_and_mac", test_if_auto_with_mtu_and_mac);
     g_test_add_func("/initrd/cmdline/if_ip4_manual", test_if_ip4_manual);
+    g_test_add_func("/initrd/cmdline/if_ip4_auto", test_if_ip4_auto);
     g_test_add_func("/initrd/cmdline/if_ip4_manual_no_dev", test_if_ip4_manual_no_dev);
     g_test_add_func("/initrd/cmdline/if_ip6_manual", test_if_ip6_manual);
     g_test_add_func("/initrd/cmdline/if_mac_ifname", test_if_mac_ifname);
diff --git a/src/nmcli/common.c b/src/nmcli/common.c
index f31d0987..518faa2f 100644
--- a/src/nmcli/common.c
+++ b/src/nmcli/common.c
@@ -635,12 +635,6 @@ vpn_openconnect_get_secrets(NMConnection *connection, GPtrArray *secrets)
 {
     GError       *error = NULL;
     NMSettingVpn *s_vpn;
-    const char   *gw, *port;
-    gs_free char *cookie  = NULL;
-    gs_free char *gateway = NULL;
-    gs_free char *gwcert  = NULL;
-    int           status  = 0;
-    int           i;
     gboolean      ret;
 
     if (!connection)
@@ -653,54 +647,15 @@ vpn_openconnect_get_secrets(NMConnection *connection, GPtrArray *secrets)
     if (!nm_streq0(nm_setting_vpn_get_service_type(s_vpn), NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT))
         return FALSE;
 
-    /* Get gateway and port */
-    gw   = nm_setting_vpn_get_data_item(s_vpn, "gateway");
-    port = gw ? strrchr(gw, ':') : NULL;
-
     /* Interactively authenticate to OpenConnect server and get secrets */
-    ret = nm_vpn_openconnect_authenticate_helper(gw, &cookie, &gateway, &gwcert, &status, &error);
+    ret = nm_vpn_openconnect_authenticate_helper(s_vpn, secrets, &error);
+
     if (!ret) {
         nmc_printerr(_("Error: openconnect failed: %s\n"), error->message);
         g_clear_error(&error);
         return FALSE;
     }
 
-    if (WIFEXITED(status)) {
-        if (WEXITSTATUS(status) != 0)
-            nmc_printerr(_("Error: openconnect failed with status %d\n"), WEXITSTATUS(status));
-    } else if (WIFSIGNALED(status))
-        nmc_printerr(_("Error: openconnect failed with signal %d\n"), WTERMSIG(status));
-
-    /* Append port to the host value */
-    if (gateway && port) {
-        gs_free char *tmp = gateway;
-
-        gateway = g_strdup_printf("%s%s", tmp, port);
-    }
-
-    /* Fill secrets to the array */
-    for (i = 0; i < secrets->len; i++) {
-        NMSecretAgentSimpleSecret *secret = secrets->pdata[i];
-
-        if (secret->secret_type != NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET)
-            continue;
-        if (!nm_streq0(secret->vpn_type, NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT))
-            continue;
-
-        if (nm_streq0(secret->entry_id, NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "cookie")) {
-            g_free(secret->value);
-            secret->value = g_steal_pointer(&cookie);
-        } else if (nm_streq0(secret->entry_id,
-                             NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gateway")) {
-            g_free(secret->value);
-            secret->value = g_steal_pointer(&gateway);
-        } else if (nm_streq0(secret->entry_id,
-                             NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gwcert")) {
-            g_free(secret->value);
-            secret->value = g_steal_pointer(&gwcert);
-        }
-    }
-
     return TRUE;
 }
 
@@ -925,7 +880,8 @@ read_again:
     rl_got_line = FALSE;
     rl_callback_handler_install(prompt, readline_cb);
 
-    while (!rl_got_line && g_main_loop_is_running(loop) && !nmc_seen_sigint())
+    while (!rl_got_line && (g_main_loop_is_running(loop) || nmc_config->offline)
+           && !nmc_seen_sigint())
         g_main_context_iteration(NULL, TRUE);
 
     /* If Ctrl-C was detected, complete the line */
@@ -954,7 +910,7 @@ read_again:
         }
     } else if (!rl_string) {
         /* Ctrl-D, exit */
-        if (g_main_loop_is_running(loop))
+        if (g_main_loop_is_running(loop) || nmc_config->offline)
             nmc_exit();
     }
 
@@ -1294,6 +1250,7 @@ got_client(GObject *source_object, GAsyncResult *res, gpointer user_data)
                                 error->message);
     } else {
         nmc->client = NM_CLIENT(source_object);
+        nmc_warn_if_version_mismatch(nmc->client);
         call_cmd(nmc,
                  g_steal_pointer(&task),
                  call->cmd,
@@ -1416,7 +1373,7 @@ call_cmd(NmCli *nmc, GTask *task, const NMCCommand *cmd, int argc, const char *c
 {
     CmdCall *call;
 
-    if (nmc->offline) {
+    if (nmc->nmc_config.offline) {
         if (!cmd->supports_offline) {
             g_task_return_new_error(task,
                                     NMCLI_ERROR,
diff --git a/src/nmcli/connections.c b/src/nmcli/connections.c
index 36de6373..d6413c91 100644
--- a/src/nmcli/connections.c
+++ b/src/nmcli/connections.c
@@ -237,7 +237,7 @@ error:
 static const GPtrArray *
 nmc_get_connections(const NmCli *nmc)
 {
-    if (nmc->offline) {
+    if (nmc->nmc_config.offline) {
         g_return_val_if_fail(!nmc->client, nmc->offline_connections);
         return nmc->offline_connections;
     } else {
@@ -251,7 +251,7 @@ nmc_get_active_connections(const NmCli *nmc)
 {
     static const GPtrArray offline_active_connections = {.len = 0};
 
-    if (nmc->offline) {
+    if (nmc->nmc_config.offline) {
         g_return_val_if_fail(!nmc->client, &offline_active_connections);
         return &offline_active_connections;
     } else {
@@ -776,7 +776,7 @@ _metagen_con_show_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS)
         if (ac)
             return nm_object_get_path(NM_OBJECT(ac));
         return NULL;
-    case NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE:
+    case NMC_GENERIC_INFO_TYPE_CON_SHOW_PORT:
         if (!s_con)
             return NULL;
         return nm_setting_connection_get_slave_type(s_con);
@@ -807,7 +807,7 @@ const NmcMetaGenericInfo *const metagen_con_show[_NMC_GENERIC_INFO_TYPE_CON_SHOW
     _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE, "DEVICE"),
     _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE, "STATE"),
     _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH, "ACTIVE-PATH"),
-    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE, "SLAVE"),
+    _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_PORT, "SLAVE"),
     _METAGEN_CON_SHOW(NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME, "FILENAME"),
 };
 #define NMC_FIELDS_CON_SHOW_COMMON "NAME,UUID,TYPE,DEVICE"
@@ -893,8 +893,8 @@ _metagen_con_active_general_get_fcn(NMC_META_GENERIC_INFO_GET_FCN_ARGS)
         /* this is really ugly, because the zone is not a property of the active-connection,
          * but the settings-connection profile. There is no guarantee, that they agree. */
         return s_con ? nm_setting_connection_get_zone(s_con) : NULL;
-    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH:
-        dev = nm_active_connection_get_master(ac);
+    case NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CONTROLLER_PATH:
+        dev = nm_active_connection_get_controller(ac);
         return dev ? nm_object_get_path(NM_OBJECT(dev)) : NULL;
     default:
         break;
@@ -922,7 +922,7 @@ const NmcMetaGenericInfo
                                     "DBUS-PATH"),
         _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH, "CON-PATH"),
         _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE, "ZONE"),
-        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH,
+        _METAGEN_CON_ACTIVE_GENERAL(NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CONTROLLER_PATH,
                                     "MASTER-PATH"),
 };
 
@@ -1046,9 +1046,9 @@ const NmcMetaGenericInfo
     "," NM_SETTING_MACVLAN_SETTING_NAME "," NM_SETTING_VXLAN_SETTING_NAME              \
     "," NM_SETTING_VRF_SETTING_NAME "," NM_SETTING_WPAN_SETTING_NAME                   \
     "," NM_SETTING_6LOWPAN_SETTING_NAME "," NM_SETTING_WIREGUARD_SETTING_NAME          \
-    "," NM_SETTING_PROXY_SETTING_NAME "," NM_SETTING_TC_CONFIG_SETTING_NAME            \
-    "," NM_SETTING_SRIOV_SETTING_NAME "," NM_SETTING_ETHTOOL_SETTING_NAME              \
-    "," NM_SETTING_OVS_DPDK_SETTING_NAME                                               \
+    "," NM_SETTING_LINK_SETTING_NAME "," NM_SETTING_PROXY_SETTING_NAME                 \
+    "," NM_SETTING_TC_CONFIG_SETTING_NAME "," NM_SETTING_SRIOV_SETTING_NAME            \
+    "," NM_SETTING_ETHTOOL_SETTING_NAME "," NM_SETTING_OVS_DPDK_SETTING_NAME           \
     "," NM_SETTING_HOSTNAME_SETTING_NAME /* NM_SETTING_DUMMY_SETTING_NAME NM_SETTING_WIMAX_SETTING_NAME */
 
 const NmcMetaGenericInfo *const nmc_fields_con_active_details_groups[] = {
@@ -2242,7 +2242,7 @@ get_connection(NmCli              *nmc,
     if (nmc->offline_connections && nmc->offline_connections->len)
         return nmc->offline_connections->pdata[0];
 
-    g_return_val_if_fail(!nmc->offline, NULL);
+    g_return_val_if_fail(!nmc->nmc_config.offline, NULL);
 
     if (*argc == 0) {
         g_set_error_literal(error,
@@ -2609,15 +2609,21 @@ get_default_active_connection(NmCli *nmc, NMDevice **device)
     return default_ac;
 }
 
-/* Find a device to activate the connection on.
- * IN:  connection:  connection to activate
- *      iface:       device interface name to use (optional)
- *      ap:          access point to use (optional; valid just for 802-11-wireless)
- *      nsp:         Network Service Provider to use (option; valid only for wimax)
- * OUT: device:      found device
- *      spec_object: specific_object path of NMAccessPoint
- * RETURNS: TRUE when a device is found, FALSE otherwise.
- */
+/**
+ * find_device_for_connection:
+ * @nmc: the #NmCli
+ * @connection: connection to activate
+ * @iface: device interface name to use (optional)
+ * @ap: access point to use (optional; valid just for 802-11-wireless)
+ * @nsp: Network Service Provider to use (option; valid only for wimax)
+ * @device: (out): found device
+ * @spec_object: (out): specific_object path of NMAccessPoint
+ * @error: the error reason.
+ *
+ * Find a device to activate the connection on.
+ *
+ * Return: TRUE when a device is found, FALSE otherwise.
+ **/
 static gboolean
 find_device_for_connection(NmCli        *nmc,
                            NMConnection *connection,
@@ -3489,19 +3495,19 @@ do_connection_down(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons
 /*****************************************************************************/
 
 /*
- * Return the most appropriate name for the connection of a type 'name' possibly with given 'slave_type'
+ * Return the most appropriate name for the connection of a type 'name' possibly with given 'port_type'
  * if exists, else return the 'name'. The returned string must not be freed.
  */
 static const char *
-get_name_alias_toplevel(const char *name, const char *slave_type)
+get_name_alias_toplevel(const char *name, const char *port_type)
 {
     const NMMetaSettingInfoEditor *setting_info;
 
-    if (slave_type) {
-        const char *slave_name;
+    if (port_type) {
+        const char *port_name;
 
-        if (nm_meta_setting_info_valid_parts_for_slave_type(slave_type, &slave_name))
-            return slave_name ?: name;
+        if (nm_meta_setting_info_valid_parts_for_slave_type(port_type, &port_name))
+            return port_name ?: name;
         return name;
     }
 
@@ -3520,7 +3526,7 @@ get_name_alias_toplevel(const char *name, const char *slave_type)
  */
 static char *
 get_valid_options_string(const NMMetaSettingValidPartItem *const *array,
-                         const NMMetaSettingValidPartItem *const *array_slv)
+                         const NMMetaSettingValidPartItem *const *array_port)
 {
     const NMMetaSettingValidPartItem *const *iter = array;
     GString                                 *str;
@@ -3528,7 +3534,7 @@ get_valid_options_string(const NMMetaSettingValidPartItem *const *array,
 
     str = g_string_sized_new(150);
 
-    for (i = 0; i < 2; i++, iter = array_slv) {
+    for (i = 0; i < 2; i++, iter = array_port) {
         for (; iter && *iter; iter++) {
             const NMMetaSettingInfoEditor *setting_info = (*iter)->setting_info;
 
@@ -3608,7 +3614,7 @@ _construct_property_name(const char            *setting_name,
 
 /* get_valid_properties_string:
  * @array: base properties for the current connection type
- * @array_slv: slave properties (or ipv4/ipv6 ones) for the current connection type
+ * @array_port: port properties (or ipv4/ipv6 ones) for the current connection type
  * @modifier: to prepend to each element of the returned list
  * @prefix: only properties matching the prefix will be returned
  * @postfix: required prefix on the property args; if a empty string is passed, is
@@ -3622,7 +3628,7 @@ _construct_property_name(const char            *setting_name,
  */
 static char *
 get_valid_properties_string(const NMMetaSettingValidPartItem *const *array,
-                            const NMMetaSettingValidPartItem *const *array_slv,
+                            const NMMetaSettingValidPartItem *const *array_port,
                             NMMetaAccessorModifier                   modifier,
                             const char                              *prefix,
                             const char                              *postfix)
@@ -3637,7 +3643,7 @@ get_valid_properties_string(const NMMetaSettingValidPartItem *const *array,
 
     str = g_string_sized_new(1024);
 
-    for (i = 0; i < 2; i++, iter = array_slv) {
+    for (i = 0; i < 2; i++, iter = array_port) {
         for (; !full_match && iter && *iter; iter++) {
             const NMMetaSettingInfoEditor *setting_info = (*iter)->setting_info;
 
@@ -3690,7 +3696,7 @@ get_valid_properties_string(const NMMetaSettingValidPartItem *const *array,
 
 /*
  * Check if 'val' is valid string in either array->name or array->alias for
- * both array parameters (array & array_slv).
+ * both array parameters (array & array_port).
  * It accepts shorter string provided they are not ambiguous.
  * 'val' == NULL doesn't hurt.
  *
@@ -3700,7 +3706,7 @@ get_valid_properties_string(const NMMetaSettingValidPartItem *const *array,
 static const char *
 check_valid_name(const char                              *val,
                  const NMMetaSettingValidPartItem *const *array,
-                 const NMMetaSettingValidPartItem *const *array_slv,
+                 const NMMetaSettingValidPartItem *const *array_port,
                  GError                                 **error)
 {
     const NMMetaSettingValidPartItem *const *iter;
@@ -3714,7 +3720,7 @@ check_valid_name(const char                              *val,
     /* Create a temporary array that can be used in nmc_string_is_valid() */
     tmp_arr = g_ptr_array_sized_new(32);
     iter    = array;
-    for (i = 0; i < 2; i++, iter = array_slv) {
+    for (i = 0; i < 2; i++, iter = array_port) {
         for (; iter && *iter; iter++) {
             const NMMetaSettingInfoEditor *setting_info = (*iter)->setting_info;
 
@@ -3734,7 +3740,7 @@ check_valid_name(const char                              *val,
             /* We want to handle aliases, so construct own error message */
             gs_free char *err_str = NULL;
 
-            err_str = get_valid_options_string(array, array_slv);
+            err_str = get_valid_options_string(array, array_port);
             g_set_error(error, 1, 0, _("'%s' not among [%s]"), val, err_str);
             g_clear_error(&tmp_err);
         }
@@ -3743,7 +3749,7 @@ check_valid_name(const char                              *val,
 
     /* Return a pointer to the found string in passed 'array' */
     iter = array;
-    for (i = 0; i < 2; i++, iter = array_slv) {
+    for (i = 0; i < 2; i++, iter = array_port) {
         for (; iter && *iter; iter++) {
             const NMMetaSettingInfoEditor *setting_info = (*iter)->setting_info;
 
@@ -3760,7 +3766,7 @@ check_valid_name(const char                              *val,
 }
 
 static const char *
-check_valid_name_toplevel(const char *val, const char **slave_type, GError **error)
+check_valid_name_toplevel(const char *val, const char **port_type, GError **error)
 {
     gs_unref_ptrarray GPtrArray   *tmp_arr = NULL;
     const NMMetaSettingInfoEditor *setting_info;
@@ -3768,7 +3774,7 @@ check_valid_name_toplevel(const char *val, const char **slave_type, GError **err
     const char                    *str;
     int                            i;
 
-    NM_SET_OUT(slave_type, NULL);
+    NM_SET_OUT(port_type, NULL);
 
     /* Create a temporary array that can be used in nmc_string_is_valid() */
     tmp_arr = g_ptr_array_sized_new(32);
@@ -3799,13 +3805,13 @@ check_valid_name_toplevel(const char *val, const char **slave_type, GError **err
     }
 
     if (nm_streq(str, "bond-slave")) {
-        NM_SET_OUT(slave_type, NM_SETTING_BOND_SETTING_NAME);
+        NM_SET_OUT(port_type, NM_SETTING_BOND_SETTING_NAME);
         return NM_SETTING_WIRED_SETTING_NAME;
     } else if (nm_streq(str, "bridge-slave")) {
-        NM_SET_OUT(slave_type, NM_SETTING_BRIDGE_SETTING_NAME);
+        NM_SET_OUT(port_type, NM_SETTING_BRIDGE_SETTING_NAME);
         return NM_SETTING_WIRED_SETTING_NAME;
     } else if (nm_streq(str, "team-slave")) {
-        NM_SET_OUT(slave_type, NM_SETTING_TEAM_SETTING_NAME);
+        NM_SET_OUT(port_type, NM_SETTING_TEAM_SETTING_NAME);
         return NM_SETTING_WIRED_SETTING_NAME;
     }
 
@@ -3853,45 +3859,45 @@ is_setting_mandatory(NMConnection *connection, NMSetting *setting)
 /*****************************************************************************/
 
 static const char *
-_strip_master_prefix(const char *master, const char *(**func)(NMConnection *) )
+_strip_controller_prefix(const char *controller, const char *(**func)(NMConnection *) )
 {
-    if (!master)
+    if (!controller)
         return NULL;
 
-    if (g_str_has_prefix(master, "ifname/")) {
-        master = master + strlen("ifname/");
+    if (g_str_has_prefix(controller, "ifname/")) {
+        controller = controller + strlen("ifname/");
         if (func)
             *func = nm_connection_get_interface_name;
-    } else if (g_str_has_prefix(master, "uuid/")) {
-        master = master + strlen("uuid/");
+    } else if (g_str_has_prefix(controller, "uuid/")) {
+        controller = controller + strlen("uuid/");
         if (func)
             *func = nm_connection_get_uuid;
-    } else if (g_str_has_prefix(master, "id/")) {
-        master = master + strlen("id/");
+    } else if (g_str_has_prefix(controller, "id/")) {
+        controller = controller + strlen("id/");
         if (func)
             *func = nm_connection_get_id;
     }
-    return master;
+    return controller;
 }
 
-/* normalized_master_for_slave:
+/* normalized_controller_for_port:
  * @connections: list af all connections
- * @master: UUID, ifname or ID of the master connection
+ * @controller: UUID, ifname or ID of the controller connection
  * @type: virtual connection type (bond, team, bridge, ...) or %NULL
  * @out_type: type of the connection that matched
  *
- * Check whether master is a valid interface name, UUID or ID of some connection,
+ * Check whether controller is a valid interface name, UUID or ID of some connection,
  * possibly of a specified @type.
  * First UUID and ifname are checked. If they don't match, ID is checked
  * and replaced by UUID on a match.
  *
- * Returns: identifier of master connection if found, %NULL otherwise
+ * Returns: identifier of controller connection if found, %NULL otherwise
  */
 static const char *
-normalized_master_for_slave(const GPtrArray *connections,
-                            const char      *master,
-                            const char      *type,
-                            const char     **out_type)
+normalized_controller_for_port(const GPtrArray *connections,
+                               const char      *controller,
+                               const char      *type,
+                               const char     **out_type)
 {
     NMConnection        *connection;
     NMSettingConnection *s_con;
@@ -3899,13 +3905,13 @@ normalized_master_for_slave(const GPtrArray *connections,
     guint                i;
     const char          *found_by_id    = NULL;
     const char          *out_type_by_id = NULL;
-    const char          *out_master     = NULL;
+    const char          *out_controller = NULL;
     const char *(*func)(NMConnection *) = NULL;
 
-    if (!master)
+    if (!controller)
         return NULL;
 
-    master = _strip_master_prefix(master, &func);
+    controller = _strip_controller_prefix(controller, &func);
     for (i = 0; i < connections->len; i++) {
         connection = NM_CONNECTION(connections->pdata[i]);
         s_con      = nm_connection_get_setting_connection(connection);
@@ -3915,46 +3921,46 @@ normalized_master_for_slave(const GPtrArray *connections,
             continue;
         if (func) {
             /* There was a prefix; only compare to that type. */
-            if (nm_streq0(master, func(connection))) {
+            if (nm_streq0(controller, func(connection))) {
                 if (out_type)
                     *out_type = con_type;
                 if (func == nm_connection_get_id)
-                    out_master = nm_connection_get_uuid(connection);
+                    out_controller = nm_connection_get_uuid(connection);
                 else
-                    out_master = master;
+                    out_controller = controller;
                 break;
             }
         } else {
             id     = nm_connection_get_id(connection);
             uuid   = nm_connection_get_uuid(connection);
             ifname = nm_connection_get_interface_name(connection);
-            if (NM_IN_STRSET(master, uuid, ifname)) {
-                out_master = master;
+            if (NM_IN_STRSET(controller, uuid, ifname)) {
+                out_controller = controller;
                 if (out_type)
                     *out_type = con_type;
                 break;
             }
-            if (!found_by_id && nm_streq0(master, id)) {
+            if (!found_by_id && nm_streq0(controller, id)) {
                 out_type_by_id = con_type;
                 found_by_id    = uuid;
             }
         }
     }
 
-    if (!out_master) {
-        out_master = found_by_id;
+    if (!out_controller) {
+        out_controller = found_by_id;
         if (out_type)
             *out_type = out_type_by_id;
     }
 
-    if (!out_master) {
-        nmc_print(_("Warning: master='%s' doesn't refer to any existing profile.\n"), master);
-        out_master = master;
+    if (!out_controller) {
+        nmc_print(_("Warning: master='%s' doesn't refer to any existing profile.\n"), controller);
+        out_controller = controller;
         if (out_type)
             *out_type = type;
     }
 
-    return out_master;
+    return out_controller;
 }
 
 #define WORD_YES "yes"
@@ -3981,13 +3987,12 @@ prompt_yes_no(gboolean default_yes, char *delim)
 static NMSetting *
 is_setting_valid(NMConnection                            *connection,
                  const NMMetaSettingValidPartItem *const *valid_settings_main,
-                 const NMMetaSettingValidPartItem *const *valid_settings_slave,
+                 const NMMetaSettingValidPartItem *const *valid_settings_port,
                  const char                              *setting)
 {
     const char *setting_name;
 
-    if (!(setting_name =
-              check_valid_name(setting, valid_settings_main, valid_settings_slave, NULL)))
+    if (!(setting_name = check_valid_name(setting, valid_settings_main, valid_settings_port, NULL)))
         return NULL;
     return nm_connection_get_setting_by_name(connection, setting_name);
 }
@@ -4004,7 +4009,7 @@ is_property_valid(NMSetting *setting, const char *property, GError **error)
 }
 
 static char *
-unique_master_iface_ifname(const GPtrArray *connections, const char *try_name)
+unique_controller_iface_ifname(const GPtrArray *connections, const char *try_name)
 {
     char *new_name;
     guint num = 0;
@@ -4052,7 +4057,7 @@ set_default_interface_name(NmCli *nmc, NMSettingConnection *s_con)
         gs_free char    *ifname = NULL;
 
         connections = nmc_get_connections(nmc);
-        ifname      = unique_master_iface_ifname(connections, default_name);
+        ifname      = unique_controller_iface_ifname(connections, default_name);
         g_object_set(s_con, NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL);
     }
 }
@@ -4405,26 +4410,26 @@ set_option(NmCli                    *nmc,
 
 /*
  * Return relevant NameItem[] tables for given connection (based on connection type
- * and slave type.
+ * and port type).
  */
 static gboolean
 con_settings(NMConnection                             *connection,
              const NMMetaSettingValidPartItem *const **type_settings,
-             const NMMetaSettingValidPartItem *const **slv_settings,
+             const NMMetaSettingValidPartItem *const **port_settings,
              GError                                  **error)
 {
     const char          *con_type;
     NMSettingConnection *s_con;
 
     g_return_val_if_fail(type_settings, FALSE);
-    g_return_val_if_fail(slv_settings, FALSE);
+    g_return_val_if_fail(port_settings, FALSE);
 
     s_con = nm_connection_get_setting_connection(connection);
     g_return_val_if_fail(s_con, FALSE);
 
-    con_type      = nm_setting_connection_get_slave_type(s_con);
-    *slv_settings = nm_meta_setting_info_valid_parts_for_slave_type(con_type, NULL);
-    if (!*slv_settings) {
+    con_type       = nm_setting_connection_get_slave_type(s_con);
+    *port_settings = nm_meta_setting_info_valid_parts_for_slave_type(con_type, NULL);
+    if (!*port_settings) {
         g_set_error(error,
                     NMCLI_ERROR,
                     NMC_RESULT_ERROR_USER_INPUT,
@@ -4516,7 +4521,7 @@ static gboolean
 enable_type_settings_and_options(NmCli *nmc, NMConnection *con, GError **error)
 {
     const NMMetaSettingValidPartItem *const *type_settings;
-    const NMMetaSettingValidPartItem *const *slv_settings;
+    const NMMetaSettingValidPartItem *const *port_settings;
     NMSettingConnection                     *s_con;
 
     s_con = nm_connection_get_setting_connection(con);
@@ -4542,10 +4547,10 @@ enable_type_settings_and_options(NmCli *nmc, NMConnection *con, GError **error)
                        NULL);
     }
 
-    if (!con_settings(con, &type_settings, &slv_settings, error))
+    if (!con_settings(con, &type_settings, &port_settings, error))
         return FALSE;
 
-    ensure_settings(con, slv_settings);
+    ensure_settings(con, port_settings);
     ensure_settings(con, type_settings);
 
     /* For some software connection types we generate the interface name for the user. */
@@ -4562,10 +4567,10 @@ set_connection_type(NmCli            *nmc,
                     gboolean          allow_reset,
                     GError          **error)
 {
-    GError     *local      = NULL;
-    const char *slave_type = NULL;
+    GError     *local     = NULL;
+    const char *port_type = NULL;
 
-    value = check_valid_name_toplevel(value, &slave_type, &local);
+    value = check_valid_name_toplevel(value, &port_type, &local);
     if (!value) {
         if (!allow_reset)
             return TRUE;
@@ -4578,12 +4583,12 @@ set_connection_type(NmCli            *nmc,
         return FALSE;
     }
 
-    if (slave_type) {
+    if (port_type) {
         if (!set_property(nmc->client,
                           con,
                           NM_SETTING_CONNECTION_SETTING_NAME,
                           NM_SETTING_CONNECTION_SLAVE_TYPE,
-                          slave_type,
+                          port_type,
                           NM_META_ACCESSOR_MODIFIER_SET,
                           error)) {
             return FALSE;
@@ -4628,16 +4633,16 @@ set_connection_iface(NmCli            *nmc,
 }
 
 static gboolean
-set_connection_master(NmCli            *nmc,
-                      NMConnection     *con,
-                      const OptionInfo *option,
-                      const char       *value,
-                      gboolean          allow_reset,
-                      GError          **error)
+set_connection_controller(NmCli            *nmc,
+                          NMConnection     *con,
+                          const OptionInfo *option,
+                          const char       *value,
+                          gboolean          allow_reset,
+                          GError          **error)
 {
     const GPtrArray     *connections;
     NMSettingConnection *s_con;
-    const char          *slave_type;
+    const char          *port_type;
 
     s_con = nm_connection_get_setting_connection(con);
     g_return_val_if_fail(s_con, FALSE);
@@ -4652,15 +4657,15 @@ set_connection_master(NmCli            *nmc,
         return FALSE;
     }
 
-    slave_type  = nm_setting_connection_get_slave_type(s_con);
+    port_type   = nm_setting_connection_get_slave_type(s_con);
     connections = nmc_get_connections(nmc);
-    value       = normalized_master_for_slave(connections, value, slave_type, &slave_type);
+    value       = normalized_controller_for_port(connections, value, port_type, &port_type);
 
     if (!set_property(nmc->client,
                       con,
                       NM_SETTING_CONNECTION_SETTING_NAME,
                       NM_SETTING_CONNECTION_SLAVE_TYPE,
-                      slave_type,
+                      port_type,
                       NM_META_ACCESSOR_MODIFIER_SET,
                       error)) {
         return FALSE;
@@ -4887,7 +4892,7 @@ _meta_abstract_get_option_info(const NMMetaAbstractInfo *abstract_info)
         OPTION_INFO(CONNECTION,
                     NM_SETTING_CONNECTION_MASTER,
                     "master",
-                    set_connection_master,
+                    set_connection_controller,
                     NULL),
         OPTION_INFO(BLUETOOTH,
                     NM_SETTING_BLUETOOTH_TYPE,
@@ -4970,21 +4975,21 @@ complete_property_name(NmCli                 *nmc,
 {
     NMSettingConnection                     *s_con;
     const NMMetaSettingValidPartItem *const *valid_settings_main;
-    const NMMetaSettingValidPartItem *const *valid_settings_slave;
+    const NMMetaSettingValidPartItem *const *valid_settings_port;
     const char                              *connection_type = NULL;
-    const char                              *slave_type      = NULL;
+    const char                              *port_type       = NULL;
     gs_free char                            *word_list       = NULL;
     NMMetaSettingType                        s;
 
     connection_type = nm_connection_get_connection_type(connection);
     s_con           = nm_connection_get_setting_connection(connection);
     if (s_con)
-        slave_type = nm_setting_connection_get_slave_type(s_con);
-    valid_settings_main  = get_valid_settings_array(connection_type);
-    valid_settings_slave = nm_meta_setting_info_valid_parts_for_slave_type(slave_type, NULL);
+        port_type = nm_setting_connection_get_slave_type(s_con);
+    valid_settings_main = get_valid_settings_array(connection_type);
+    valid_settings_port = nm_meta_setting_info_valid_parts_for_slave_type(port_type, NULL);
 
     word_list = get_valid_properties_string(valid_settings_main,
-                                            valid_settings_slave,
+                                            valid_settings_port,
                                             modifier,
                                             prefix,
                                             postfix);
@@ -5178,13 +5183,13 @@ nmc_process_connection_properties(NmCli              *nmc,
                                   gboolean            allow_setting_removal,
                                   GError            **error)
 {
-    /* First check if we have a slave-type, as this would mean we will not
-     * have ip properties but possibly others, slave-type specific.
+    /* First check if we have a port-type, as this would mean we will not
+     * have ip properties but possibly others, port-type specific.
      */
     /* Go through arguments and set properties */
     do {
         const NMMetaSettingValidPartItem *const *type_settings;
-        const NMMetaSettingValidPartItem *const *slv_settings;
+        const NMMetaSettingValidPartItem *const *port_settings;
         NMMetaAccessorModifier                   modifier;
         const char                              *option_orig;
         const char                              *option;
@@ -5195,10 +5200,10 @@ nmc_process_connection_properties(NmCli              *nmc,
         const char                              *chosen_option       = NULL;
         NMMetaSettingType                        s;
 
-        if (!con_settings(connection, &type_settings, &slv_settings, error))
+        if (!con_settings(connection, &type_settings, &port_settings, error))
             return FALSE;
 
-        ensure_settings(connection, slv_settings);
+        ensure_settings(connection, port_settings);
         ensure_settings(connection, type_settings);
 
         if (*argc <= 0) {
@@ -5255,9 +5260,9 @@ nmc_process_connection_properties(NmCli              *nmc,
             (*argc)--;
             (*argv)++;
 
-            ss = is_setting_valid(connection, type_settings, slv_settings, setting_name);
+            ss = is_setting_valid(connection, type_settings, port_settings, setting_name);
             if (!ss) {
-                if (!check_valid_name(setting_name, type_settings, slv_settings, NULL)) {
+                if (!check_valid_name(setting_name, type_settings, port_settings, NULL)) {
                     g_set_error(error,
                                 NMCLI_ERROR,
                                 NMC_RESULT_ERROR_USER_INPUT,
@@ -5287,7 +5292,7 @@ nmc_process_connection_properties(NmCli              *nmc,
                 complete_property_name(nmc, connection, modifier, option_sett, option_prop);
 
             option_sett_expanded =
-                check_valid_name(option_sett, type_settings, slv_settings, &local);
+                check_valid_name(option_sett, type_settings, port_settings, &local);
             if (!option_sett_expanded) {
                 g_set_error(error,
                             NMCLI_ERROR,
@@ -5328,7 +5333,7 @@ nmc_process_connection_properties(NmCli              *nmc,
 
             if (!check_valid_name(nm_meta_setting_infos[s].setting_name,
                                   type_settings,
-                                  slv_settings,
+                                  port_settings,
                                   NULL))
                 continue;
 
@@ -5838,7 +5843,7 @@ again:
 static void
 nmc_add_connection(NmCli *nmc, NMConnection *connection, gboolean temporary)
 {
-    if (nmc->offline) {
+    if (nmc->nmc_config.offline) {
         nmc_print_connection_and_quit(nmc, connection);
     } else {
         add_connection(nmc->client,
@@ -5920,8 +5925,8 @@ read_properties:
     if (nmc->ask)
         questionnaire_mandatory(nmc, connection);
 
-    /* Traditionally, we didn't ask for these options for ethernet slaves. They don't
-     * make much sense, since these are likely to be set by the master anyway. */
+    /* Traditionally, we didn't ask for these options for ethernet ports. They don't
+     * make much sense, since these are likely to be set by the controller anyway. */
     if (nm_setting_connection_get_slave_type(s_con)) {
         disable_options(NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MTU);
         disable_options(NM_SETTING_WIRED_SETTING_NAME, NM_SETTING_WIRED_MAC_ADDRESS);
@@ -5931,9 +5936,9 @@ read_properties:
     /* Connection id is special in that it's required but we don't insist
      * on getting it from the user -- we just make up something sensible. */
     if (!nm_setting_connection_get_id(s_con)) {
-        const char *ifname     = nm_setting_connection_get_interface_name(s_con);
-        const char *type       = nm_setting_connection_get_connection_type(s_con);
-        const char *slave_type = nm_setting_connection_get_slave_type(s_con);
+        const char *ifname    = nm_setting_connection_get_interface_name(s_con);
+        const char *type      = nm_setting_connection_get_connection_type(s_con);
+        const char *port_type = nm_setting_connection_get_slave_type(s_con);
 
         /* If only bother when there's a type, which is not guaranteed at this point.
          * Otherwise, the validation will fail anyway. */
@@ -5944,8 +5949,8 @@ read_properties:
 
             connections = nmc_get_connections(nmc);
             try_name =
-                ifname ? g_strdup_printf("%s-%s", get_name_alias_toplevel(type, slave_type), ifname)
-                       : g_strdup(get_name_alias_toplevel(type, slave_type));
+                ifname ? g_strdup_printf("%s-%s", get_name_alias_toplevel(type, port_type), ifname)
+                       : g_strdup(get_name_alias_toplevel(type, port_type));
             default_name = nmc_unique_connection_name(connections, try_name);
             g_object_set(s_con, NM_SETTING_CONNECTION_ID, default_name, NULL);
         }
@@ -6169,7 +6174,7 @@ gen_connection_types(const char *text)
 static char *
 gen_setting_names(const char *text, int state)
 {
-    static int                               list_idx, len, is_slv;
+    static int                               list_idx, len, is_port;
     const char                              *s_name, *a_name;
     const NMMetaSettingValidPartItem *const *valid_settings_arr;
     NMSettingConnection                     *s_con;
@@ -6178,10 +6183,10 @@ gen_setting_names(const char *text, int state)
     if (!state) {
         list_idx = 0;
         len      = strlen(text);
-        is_slv   = 0;
+        is_port  = 0;
     }
 
-    if (!is_slv) {
+    if (!is_port) {
         valid_settings_arr = get_valid_settings_array(nmc_tab_completion.con_type);
         if (list_idx >= NM_PTRARRAY_LEN(valid_settings_arr))
             return NULL;
@@ -6202,10 +6207,10 @@ gen_setting_names(const char *text, int state)
 
         /* Let's give a try to parameters related to slave type */
         list_idx = 0;
-        is_slv   = 1;
+        is_port  = 1;
     }
 
-    /* is_slv */
+    /* is_port */
     s_con = nm_connection_get_setting_connection(nmc_tab_completion.connection);
     if (s_con)
         s_type = nm_setting_connection_get_slave_type(s_con);
@@ -6241,9 +6246,9 @@ gen_property_names(const char *text, int state)
     const char                              *setting_name;
     char                                   **strv = NULL;
     const NMMetaSettingValidPartItem *const *valid_settings_main;
-    const NMMetaSettingValidPartItem *const *valid_settings_slave;
+    const NMMetaSettingValidPartItem *const *valid_settings_port;
     const char                              *p1;
-    const char                              *slv_type;
+    const char                              *port_type;
 
     /* Try to get the setting from 'line' - setting_name.property */
     p1 = strchr(line, '.');
@@ -6255,20 +6260,20 @@ gen_property_names(const char *text, int state)
 
         valid_settings_main = get_valid_settings_array(nmc_tab_completion.con_type);
 
-        /* Support autocompletion of slave-connection parameters
-         * guessing the slave type from the setting name already
+        /* Support autocompletion of port-connection parameters
+         * guessing the port type from the setting name already
          * typed (or autocompleted) */
         if (nm_streq0(strv[0], NM_SETTING_TEAM_PORT_SETTING_NAME))
-            slv_type = NM_SETTING_TEAM_SETTING_NAME;
+            port_type = NM_SETTING_TEAM_SETTING_NAME;
         else if (nm_streq0(strv[0], NM_SETTING_BRIDGE_PORT_SETTING_NAME))
-            slv_type = NM_SETTING_BRIDGE_SETTING_NAME;
+            port_type = NM_SETTING_BRIDGE_SETTING_NAME;
         else if (nm_streq0(strv[0], NM_SETTING_BOND_PORT_SETTING_NAME))
-            slv_type = NM_SETTING_BOND_SETTING_NAME;
+            port_type = NM_SETTING_BOND_SETTING_NAME;
         else
-            slv_type = NULL;
-        valid_settings_slave = nm_meta_setting_info_valid_parts_for_slave_type(slv_type, NULL);
+            port_type = NULL;
+        valid_settings_port = nm_meta_setting_info_valid_parts_for_slave_type(port_type, NULL);
 
-        setting_name = check_valid_name(strv[0], valid_settings_main, valid_settings_slave, NULL);
+        setting_name = check_valid_name(strv[0], valid_settings_main, valid_settings_port, NULL);
         if (setting_name) {
             setting = nm_meta_setting_info_editor_new_setting(
                 nm_meta_setting_info_editor_find_by_name(setting_name, FALSE),
@@ -6479,17 +6484,17 @@ should_complete_cmd(const char *line, int end, const char *cmd, int *cw_num, cha
     return ret;
 }
 
-/*
+/**
  * extract_setting_and_property:
- * prompt: (in) (allow-none): prompt string, or NULL
- * line: (in) (allow-none): line, or NULL
- * setting: (out) (transfer full) (array zero-terminated=1):
+ * @prompt: (nullable): prompt string, or NULL
+ * @line: (nullable): line, or NULL
+ * @setting: (out) (transfer full) (array zero-terminated=1) (optional):
  *   return location for setting name
- * property: (out) (transfer full) (array zero-terminated=1):
+ * @property: (out) (transfer full) (array zero-terminated=1) (optional):
  *   return location for property name
  *
  * Extract setting and property names from prompt and/or line.
- */
+ **/
 static void
 extract_setting_and_property(const char *prompt, const char *line, char **setting, char **property)
 {
@@ -6560,7 +6565,7 @@ get_setting_and_property(const char *prompt,
                          char      **property_out)
 {
     const NMMetaSettingValidPartItem *const *valid_settings_main;
-    const NMMetaSettingValidPartItem *const *valid_settings_slave;
+    const NMMetaSettingValidPartItem *const *valid_settings_port;
     gs_unref_object NMSetting               *setting  = NULL;
     gs_free char                            *property = NULL;
     NMSettingConnection                     *s_con;
@@ -6577,10 +6582,10 @@ get_setting_and_property(const char *prompt,
         if (s_con)
             s_type = nm_setting_connection_get_slave_type(s_con);
 
-        valid_settings_main  = get_valid_settings_array(nmc_tab_completion.con_type);
-        valid_settings_slave = nm_meta_setting_info_valid_parts_for_slave_type(s_type, NULL);
+        valid_settings_main = get_valid_settings_array(nmc_tab_completion.con_type);
+        valid_settings_port = nm_meta_setting_info_valid_parts_for_slave_type(s_type, NULL);
 
-        setting_name = check_valid_name(sett, valid_settings_main, valid_settings_slave, NULL);
+        setting_name = check_valid_name(sett, valid_settings_main, valid_settings_port, NULL);
         setting      = nm_meta_setting_info_editor_new_setting(
             nm_meta_setting_info_editor_find_by_name(setting_name, FALSE),
             NM_META_ACCESSOR_SETTING_INIT_TYPE_DEFAULT);
@@ -7555,7 +7560,7 @@ confirm_quit(const NmcConfig *nmc_config)
 
 /*
  * Submenu for detailed property editing
- * Return: TRUE - continue;  FALSE - should quit
+ * Returns TRUE for continue; FALSE for should quit.
  */
 static gboolean
 property_edit_submenu(NmCli               *nmc,
@@ -7757,13 +7762,13 @@ split_editor_main_cmd_args(const char *str, char **setting, char **property, cha
 static NMSetting *
 create_setting_by_name(const char                              *name,
                        const NMMetaSettingValidPartItem *const *valid_settings_main,
-                       const NMMetaSettingValidPartItem *const *valid_settings_slave)
+                       const NMMetaSettingValidPartItem *const *valid_settings_port)
 {
     const char *setting_name;
     NMSetting  *setting = NULL;
 
     /* Get a valid setting name */
-    setting_name = check_valid_name(name, valid_settings_main, valid_settings_slave, NULL);
+    setting_name = check_valid_name(name, valid_settings_main, valid_settings_port, NULL);
 
     if (setting_name) {
         setting = nm_meta_setting_info_editor_new_setting(
@@ -7777,7 +7782,7 @@ static const char *
 ask_check_setting(const NmcConfig                         *nmc_config,
                   const char                              *arg,
                   const NMMetaSettingValidPartItem *const *valid_settings_main,
-                  const NMMetaSettingValidPartItem *const *valid_settings_slave,
+                  const NMMetaSettingValidPartItem *const *valid_settings_port,
                   const char                              *valid_settings_str)
 {
     gs_free char *setting_name_user = NULL;
@@ -7794,7 +7799,7 @@ ask_check_setting(const NmcConfig                         *nmc_config,
 
     if (!(setting_name = check_valid_name(setting_name_user,
                                           valid_settings_main,
-                                          valid_settings_slave,
+                                          valid_settings_port,
                                           &err))) {
         nmc_print(_("Error: invalid setting name; %s\n"), err->message);
         g_clear_error(&err);
@@ -7937,7 +7942,7 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
     NmcEditorMainCmd                         cmd;
     gboolean                                 cmd_loop = TRUE;
     const NMMetaSettingValidPartItem *const *valid_settings_main;
-    const NMMetaSettingValidPartItem *const *valid_settings_slave;
+    const NMMetaSettingValidPartItem *const *valid_settings_port;
     gs_free char                            *valid_settings_str = NULL;
     const char                              *s_type             = NULL;
     gboolean                                 temp_changes;
@@ -7948,10 +7953,10 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
     if (s_con)
         s_type = nm_setting_connection_get_slave_type(s_con);
 
-    valid_settings_main  = get_valid_settings_array(connection_type);
-    valid_settings_slave = nm_meta_setting_info_valid_parts_for_slave_type(s_type, NULL);
+    valid_settings_main = get_valid_settings_array(connection_type);
+    valid_settings_port = nm_meta_setting_info_valid_parts_for_slave_type(s_type, NULL);
 
-    valid_settings_str = get_valid_options_string(valid_settings_main, valid_settings_slave);
+    valid_settings_str = get_valid_options_string(valid_settings_main, valid_settings_port);
     nmc_print(_("You may edit the following settings: %s\n"), valid_settings_str);
 
     menu_ctx.main_prompt = nmc_colorize(&nmc->nmc_config, NM_META_COLOR_PROMPT, BASE_PROMPT);
@@ -8051,12 +8056,12 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
                     /* setting provided as "setting.property" */
                     ss = is_setting_valid(connection,
                                           valid_settings_main,
-                                          valid_settings_slave,
+                                          valid_settings_port,
                                           cmd_arg_s);
                     if (!ss) {
                         ss_created = create_setting_by_name(cmd_arg_s,
                                                             valid_settings_main,
-                                                            valid_settings_slave);
+                                                            valid_settings_port);
                         ss         = ss_created;
                         if (!ss) {
                             nmc_print(_("Error: invalid setting argument '%s'; valid are [%s]\n"),
@@ -8128,7 +8133,7 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
                 setting_name = ask_check_setting(&nmc->nmc_config,
                                                  user_arg,
                                                  valid_settings_main,
-                                                 valid_settings_slave,
+                                                 valid_settings_port,
                                                  valid_settings_str);
                 if (!setting_name)
                     break;
@@ -8229,12 +8234,12 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
                 if (user_s) {
                     ss = is_setting_valid(connection,
                                           valid_settings_main,
-                                          valid_settings_slave,
+                                          valid_settings_port,
                                           user_s);
                     if (!ss) {
                         if (check_valid_name(user_s,
                                              valid_settings_main,
-                                             valid_settings_slave,
+                                             valid_settings_port,
                                              NULL)) {
                             nmc_print(_("Setting '%s' is not present in the connection.\n"),
                                       user_s);
@@ -8283,7 +8288,7 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
                         /* If the string is not a property, try it as a setting */
                         s_tmp = is_setting_valid(connection,
                                                  valid_settings_main,
-                                                 valid_settings_slave,
+                                                 valid_settings_port,
                                                  cmd_arg_p);
                         if (s_tmp) {
                             gs_free_error GError *local = NULL;
@@ -8340,12 +8345,12 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
                 if (user_s) {
                     ss = is_setting_valid(connection,
                                           valid_settings_main,
-                                          valid_settings_slave,
+                                          valid_settings_port,
                                           user_s);
                     if (!ss) {
                         ss = create_setting_by_name(user_s,
                                                     valid_settings_main,
-                                                    valid_settings_slave);
+                                                    valid_settings_port);
                         if (!ss) {
                             nmc_print(_("Error: invalid setting argument '%s'; valid are [%s]\n"),
                                       user_s,
@@ -8379,7 +8384,7 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
 
                         s_tmp = is_setting_valid(connection,
                                                  valid_settings_main,
-                                                 valid_settings_slave,
+                                                 valid_settings_port,
                                                  cmd_arg_p);
                         if (s_tmp)
                             print_setting_description(s_tmp);
@@ -8411,7 +8416,7 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
 
                         s_name = check_valid_name(user_s,
                                                   valid_settings_main,
-                                                  valid_settings_slave,
+                                                  valid_settings_port,
                                                   NULL);
                         if (!s_name) {
                             nmc_print(_("Error: unknown setting: '%s'\n"), user_s);
@@ -8445,7 +8450,7 @@ editor_menu_main(NmCli *nmc, NMConnection *connection, const char *connection_ty
                             NMSetting *s_tmp;
                             s_tmp = is_setting_valid(connection,
                                                      valid_settings_main,
-                                                     valid_settings_slave,
+                                                     valid_settings_port,
                                                      cmd_arg_p);
                             if (s_tmp) {
                                 /* Print the whole setting */
@@ -8794,7 +8799,7 @@ get_ethernet_device_name(NmCli *nmc)
 }
 
 static void
-editor_init_new_connection(NmCli *nmc, NMConnection *connection, const char *slave_type)
+editor_init_new_connection(NmCli *nmc, NMConnection *connection, const char *port_type)
 {
     NMSetting           *setting, *base_setting;
     NMSettingConnection *s_con;
@@ -8809,10 +8814,10 @@ editor_init_new_connection(NmCli *nmc, NMConnection *connection, const char *sla
 
     nmc_setting_connection_connect_handlers(s_con, connection);
 
-    if (slave_type) {
+    if (port_type) {
         const char *dev_ifname = get_ethernet_device_name(nmc);
 
-        /* For bond/team/bridge slaves add 'wired' setting */
+        /* For bond/team/bridge ports add 'wired' setting */
         setting = nm_setting_wired_new();
         nm_connection_add_setting(connection, setting);
 
@@ -8822,7 +8827,7 @@ editor_init_new_connection(NmCli *nmc, NMConnection *connection, const char *sla
                      NM_SETTING_CONNECTION_MASTER,
                      dev_ifname ?: "eth0",
                      NM_SETTING_CONNECTION_SLAVE_TYPE,
-                     slave_type,
+                     port_type,
                      NULL);
     } else {
         const NMMetaSettingInfoEditor *setting_info;
@@ -9020,7 +9025,7 @@ do_connection_edit(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons
 
         editor_init_existing_connection(connection);
     } else {
-        const char   *slave_type   = NULL;
+        const char   *port_type    = NULL;
         gs_free char *uuid         = NULL;
         gs_free char *default_name = NULL;
         gs_free char *tmp_str      = NULL;
@@ -9032,7 +9037,7 @@ do_connection_edit(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons
             return;
         }
 
-        connection_type = check_valid_name_toplevel(type, &slave_type, &err1);
+        connection_type = check_valid_name_toplevel(type, &port_type, &err1);
         tmp_str         = get_valid_options_string_toplevel();
 
         while (!connection_type) {
@@ -9046,7 +9051,7 @@ do_connection_edit(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons
 
             type_ask = nmc_readline(&nmc->nmc_config, EDITOR_PROMPT_CON_TYPE);
             type = type_ask = nm_strstrip(type_ask);
-            connection_type = check_valid_name_toplevel(type_ask, &slave_type, &err1);
+            connection_type = check_valid_name_toplevel(type_ask, &port_type, &err1);
         }
         nm_clear_g_free(&tmp_str);
 
@@ -9073,7 +9078,7 @@ do_connection_edit(const NMCCommand *cmd, NmCli *nmc, int argc, const char *cons
         nm_connection_add_setting(connection, NM_SETTING(s_con));
 
         /* Initialize the new connection so that it is valid from the start */
-        editor_init_new_connection(nmc, connection, slave_type);
+        editor_init_new_connection(nmc, connection, port_type);
     }
 
     /* nmcli runs the editor */
@@ -9140,7 +9145,7 @@ modify_connection_cb(GObject *connection, GAsyncResult *result, gpointer user_da
 static void
 nmc_update_connection(NmCli *nmc, NMConnection *connection, gboolean temporary)
 {
-    if (nmc->offline) {
+    if (nmc->nmc_config.offline) {
         nmc_print_connection_and_quit(nmc, connection);
     } else {
         nm_remote_connection_commit_changes_async(NM_REMOTE_CONNECTION(connection),
@@ -9171,7 +9176,7 @@ do_connection_modify(const NMCCommand *cmd, NmCli *nmc, int argc, const char *co
     }
 
     /* Don't insist on having argument if we're running in offline mode. */
-    if (!nmc->offline || argc > 0) {
+    if (!nmc->nmc_config.offline || argc > 0) {
         if (!nmc_process_connection_properties(nmc, connection, &argc, &argv, TRUE, &error)) {
             g_string_assign(nmc->return_text, error->message);
             nmc->return_value = error->code;
diff --git a/src/nmcli/devices.c b/src/nmcli/devices.c
index d408b47f..a56f0f81 100644
--- a/src/nmcli/devices.c
+++ b/src/nmcli/devices.c
@@ -1376,6 +1376,10 @@ fill_output_access_point(NMAccessPoint *ap, const APInfo *info)
             || (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X)) {
             g_string_append(security_str, "802.1X ");
         }
+        if ((wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)
+            || (rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)) {
+            g_string_append(security_str, "WPA-EAP-SUITE-B-192 ");
+        }
     }
 
     if (security_str->len > 0)
@@ -1466,32 +1470,30 @@ print_bond_bridge_info(NMDevice   *device,
                        const char *group_prefix,
                        const char *one_field)
 {
-    const GPtrArray                 *slaves = NULL;
-    GString                         *slaves_str;
+    const GPtrArray                 *ports = NULL;
+    GString                         *ports_str;
     int                              idx;
     const NMMetaAbstractInfo *const *tmpl;
     NmcOutputField                  *arr;
     NMC_OUTPUT_DATA_DEFINE_SCOPED(out);
 
-    if (NM_IS_DEVICE_BOND(device))
-        slaves = nm_device_bond_get_slaves(NM_DEVICE_BOND(device));
-    else if (NM_IS_DEVICE_BRIDGE(device))
-        slaves = nm_device_bridge_get_slaves(NM_DEVICE_BRIDGE(device));
+    if (NM_IS_DEVICE_BOND(device) || NM_IS_DEVICE_BRIDGE(device))
+        ports = nm_device_get_ports(device);
     else
         g_return_val_if_reached(FALSE);
 
-    slaves_str = g_string_new(NULL);
-    for (idx = 0; slaves && idx < slaves->len; idx++) {
-        NMDevice   *slave = g_ptr_array_index(slaves, idx);
-        const char *iface = nm_device_get_iface(slave);
+    ports_str = g_string_new(NULL);
+    for (idx = 0; ports && idx < ports->len; idx++) {
+        NMDevice   *port  = g_ptr_array_index(ports, idx);
+        const char *iface = nm_device_get_iface(port);
 
         if (iface) {
-            g_string_append(slaves_str, iface);
-            g_string_append_c(slaves_str, ' ');
+            g_string_append(ports_str, iface);
+            g_string_append_c(ports_str, ' ');
         }
     }
-    if (slaves_str->len > 0)
-        g_string_truncate(slaves_str, slaves_str->len - 1); /* Chop off last space */
+    if (ports_str->len > 0)
+        g_string_truncate(ports_str, ports_str->len - 1); /* Chop off last space */
 
     tmpl        = (const NMMetaAbstractInfo *const *) nmc_fields_dev_show_master_prop;
     out_indices = parse_output_fields(one_field, tmpl, FALSE, NULL, NULL);
@@ -1500,7 +1502,7 @@ print_bond_bridge_info(NMDevice   *device,
 
     arr = nmc_dup_fields_array(tmpl, NMC_OF_FLAG_SECTION_PREFIX);
     set_val_strc(arr, 0, group_prefix); /* i.e. BOND, TEAM, BRIDGE */
-    set_val_str(arr, 1, g_string_free(slaves_str, FALSE));
+    set_val_str(arr, 1, g_string_free(ports_str, FALSE));
     g_ptr_array_add(out.output_data, arr);
 
     print_data_prepare_width(out.output_data);
@@ -1531,30 +1533,30 @@ sanitize_team_config(const char *config)
 static gboolean
 print_team_info(NMDevice *device, NmCli *nmc, const char *group_prefix, const char *one_field)
 {
-    const GPtrArray                 *slaves = NULL;
-    GString                         *slaves_str;
+    const GPtrArray                 *ports = NULL;
+    GString                         *ports_str;
     int                              idx;
     const NMMetaAbstractInfo *const *tmpl;
     NmcOutputField                  *arr;
     NMC_OUTPUT_DATA_DEFINE_SCOPED(out);
 
     if (NM_IS_DEVICE_TEAM(device))
-        slaves = nm_device_team_get_slaves(NM_DEVICE_TEAM(device));
+        ports = nm_device_get_ports(device);
     else
         g_return_val_if_reached(FALSE);
 
-    slaves_str = g_string_new(NULL);
-    for (idx = 0; slaves && idx < slaves->len; idx++) {
-        NMDevice   *slave = g_ptr_array_index(slaves, idx);
-        const char *iface = nm_device_get_iface(slave);
+    ports_str = g_string_new(NULL);
+    for (idx = 0; ports && idx < ports->len; idx++) {
+        NMDevice   *port  = g_ptr_array_index(ports, idx);
+        const char *iface = nm_device_get_iface(port);
 
         if (iface) {
-            g_string_append(slaves_str, iface);
-            g_string_append_c(slaves_str, ' ');
+            g_string_append(ports_str, iface);
+            g_string_append_c(ports_str, ' ');
         }
     }
-    if (slaves_str->len > 0)
-        g_string_truncate(slaves_str, slaves_str->len - 1); /* Chop off last space */
+    if (ports_str->len > 0)
+        g_string_truncate(ports_str, ports_str->len - 1); /* Chop off last space */
 
     tmpl        = (const NMMetaAbstractInfo *const *) nmc_fields_dev_show_team_prop;
     out_indices = parse_output_fields(one_field, tmpl, FALSE, NULL, NULL);
@@ -1563,7 +1565,7 @@ print_team_info(NMDevice *device, NmCli *nmc, const char *group_prefix, const ch
 
     arr = nmc_dup_fields_array(tmpl, NMC_OF_FLAG_SECTION_PREFIX);
     set_val_strc(arr, 0, group_prefix); /* TEAM */
-    set_val_str(arr, 1, g_string_free(slaves_str, FALSE));
+    set_val_str(arr, 1, g_string_free(ports_str, FALSE));
     set_val_str(arr, 2, sanitize_team_config(nm_device_team_get_config(NM_DEVICE_TEAM(device))));
     g_ptr_array_add(out.output_data, arr);
 
diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.c b/src/nmcli/gen-metadata-nm-settings-nmcli.c
index 196e4d35..8e5831ac 100644
--- a/src/nmcli/gen-metadata-nm-settings-nmcli.c
+++ b/src/nmcli/gen-metadata-nm-settings-nmcli.c
@@ -57,10 +57,22 @@ main(int argc, char *argv[])
                 g_print("\n%salias=%s",
                         _indent_level(2 * INDENT + 10),
                         tmp3 = _xml_escape_attribute(prop_info->property_alias));
-            if (prop_info->describe_doc)
-                g_print("\n%sdescription=%s",
+            if (prop_info->describe_doc) {
+                /* These descriptions are used by interactive nmcli modes. For the most part,
+                 * they are themselves generated (see "settings-docs.h"). Some of them are instead
+                 * explicitly (manually) set.
+                 *
+                 * In any case, they serve little purpose outside of nmcli's interactive mode,
+                 * because their formatting/wording would not be suitable.
+                 *
+                 * We generate this XML mainly to generate `man nm-settings-nmcli`, but the
+                 * descriptions in "describe_doc" field are not suitable there.
+                 *
+                 * Name them something else ("<nmcli-description>") which isn't actually used. */
+                g_print("\n%snmcli-description=%s",
                         _indent_level(2 * INDENT + 10),
                         tmp4 = _xml_escape_attribute(prop_info->describe_doc));
+            }
             g_print(" />\n");
         }
 
diff --git a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
index e0fa54ef..ef0973ca 100644
--- a/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
+++ b/src/nmcli/gen-metadata-nm-settings-nmcli.xml.in
@@ -2,466 +2,460 @@
     <setting name="6lowpan" >
         <property name="parent"
                   alias="dev"
-                  description="If given, specifies the parent interface name or parent connection UUID from which this 6LowPAN interface should be created." />
+                  nmcli-description="If given, specifies the parent interface name or parent connection UUID from which this 6LowPAN interface should be created." />
     </setting>
     <setting name="802-11-olpc-mesh"
              alias="olpc-mesh" >
         <property name="ssid"
                   alias="ssid"
-                  description="SSID of the mesh network to join." />
+                  nmcli-description="SSID of the mesh network to join." />
         <property name="channel"
                   alias="channel"
-                  description="Channel on which the mesh network to join is located." />
+                  nmcli-description="Channel on which the mesh network to join is located." />
         <property name="dhcp-anycast-address"
                   alias="dhcp-anycast"
-                  description="Anycast DHCP MAC address used when requesting an IP address via DHCP. The specific anycast address used determines which DHCP server class answers the request. This is currently only implemented by dhclient DHCP plugin." />
+                  nmcli-description="Anycast DHCP MAC address used when requesting an IP address via DHCP. The specific anycast address used determines which DHCP server class answers the request. This is currently only implemented by dhclient DHCP plugin." />
     </setting>
     <setting name="802-11-wireless"
              alias="wifi" >
         <property name="ssid"
                   alias="ssid"
-                  description="SSID of the Wi-Fi network. Must be specified." />
+                  nmcli-description="SSID of the Wi-Fi network. Must be specified." />
         <property name="mode"
                   alias="mode"
-                  description="Wi-Fi network mode; one of &quot;infrastructure&quot;, &quot;mesh&quot;, &quot;adhoc&quot; or &quot;ap&quot;.  If blank, infrastructure is assumed." />
+                  nmcli-description="Wi-Fi network mode; one of &quot;infrastructure&quot;, &quot;mesh&quot;, &quot;adhoc&quot; or &quot;ap&quot;.  If blank, infrastructure is assumed." />
         <property name="band"
-                  description="802.11 frequency band of the network.  One of &quot;a&quot; for 5GHz 802.11a or &quot;bg&quot; for 2.4GHz 802.11.  This will lock associations to the Wi-Fi network to the specific band, i.e. if &quot;a&quot; is specified, the device will not associate with the same network in the 2.4GHz band even if the network&apos;s settings are compatible.  This setting depends on specific driver capability and may not work with all drivers." />
+                  nmcli-description="802.11 frequency band of the network.  One of &quot;a&quot; for 5GHz 802.11a or &quot;bg&quot; for 2.4GHz 802.11.  This will lock associations to the Wi-Fi network to the specific band, i.e. if &quot;a&quot; is specified, the device will not associate with the same network in the 2.4GHz band even if the network&apos;s settings are compatible.  This setting depends on specific driver capability and may not work with all drivers." />
         <property name="channel"
-                  description="Wireless channel to use for the Wi-Fi connection.  The device will only join (or create for Ad-Hoc networks) a Wi-Fi network on the specified channel.  Because channel numbers overlap between bands, this property also requires the &quot;band&quot; property to be set." />
+                  nmcli-description="Wireless channel to use for the Wi-Fi connection.  The device will only join (or create for Ad-Hoc networks) a Wi-Fi network on the specified channel.  Because channel numbers overlap between bands, this property also requires the &quot;band&quot; property to be set." />
         <property name="bssid"
-                  description="If specified, directs the device to only associate with the given access point.  This capability is highly driver dependent and not supported by all devices.  Note: this property does not control the BSSID used when creating an Ad-Hoc network and is unlikely to in the future. Locking a client profile to a certain BSSID will prevent roaming and also disable background scanning. That can be useful, if there is only one access point for the SSID." />
-        <property name="rate"
-                  description="If non-zero, directs the device to only use the specified bitrate for communication with the access point.  Units are in Kb/s, ie 5500 = 5.5 Mbit/s.  This property is highly driver dependent and not all devices support setting a static bitrate." />
-        <property name="tx-power"
-                  description="If non-zero, directs the device to use the specified transmit power. Units are dBm.  This property is highly driver dependent and not all devices support setting a static transmit power." />
+                  nmcli-description="If specified, directs the device to only associate with the given access point.  This capability is highly driver dependent and not supported by all devices.  Note: this property does not control the BSSID used when creating an Ad-Hoc network and is unlikely to in the future. Locking a client profile to a certain BSSID will prevent roaming and also disable background scanning. That can be useful, if there is only one access point for the SSID." />
         <property name="mac-address"
                   alias="mac"
-                  description="If specified, this connection will only apply to the Wi-Fi device whose permanent MAC address matches. This property does not change the MAC address of the device (i.e. MAC spoofing)." />
+                  nmcli-description="If specified, this connection will only apply to the Wi-Fi device whose permanent MAC address matches. This property does not change the MAC address of the device (i.e. MAC spoofing)." />
         <property name="cloned-mac-address"
                   alias="cloned-mac"
-                  description="If specified, request that the device use this MAC address instead. This is known as MAC cloning or spoofing. Beside explicitly specifying a MAC address, the special values &quot;preserve&quot;, &quot;permanent&quot;, &quot;random&quot; and &quot;stable&quot; are supported. &quot;preserve&quot; means not to touch the MAC address on activation. &quot;permanent&quot; means to use the permanent hardware address of the device. &quot;random&quot; creates a random MAC address on each connect. &quot;stable&quot; creates a hashed MAC address based on connection.stable-id and a machine dependent key. If unspecified, the value can be overwritten via global defaults, see manual of NetworkManager.conf. If still unspecified, it defaults to &quot;preserve&quot; (older versions of NetworkManager may use a different default value). On D-Bus, this field is expressed as &quot;assigned-mac-address&quot; or the deprecated &quot;cloned-mac-address&quot;." />
+                  nmcli-description="If specified, request that the device use this MAC address instead. This is known as MAC cloning or spoofing. Beside explicitly specifying a MAC address, the special values &quot;preserve&quot;, &quot;permanent&quot;, &quot;random&quot; and &quot;stable&quot; are supported. &quot;preserve&quot; means not to touch the MAC address on activation. &quot;permanent&quot; means to use the permanent hardware address of the device. &quot;random&quot; creates a random MAC address on each connect. &quot;stable&quot; creates a hashed MAC address based on connection.stable-id and a machine dependent key. If unspecified, the value can be overwritten via global defaults, see manual of NetworkManager.conf. If still unspecified, it defaults to &quot;preserve&quot; (older versions of NetworkManager may use a different default value). On D-Bus, this field is expressed as &quot;assigned-mac-address&quot; or the deprecated &quot;cloned-mac-address&quot;." />
         <property name="generate-mac-address-mask"
-                  description="With &quot;cloned-mac-address&quot; setting &quot;random&quot; or &quot;stable&quot;, by default all bits of the MAC address are scrambled and a locally-administered, unicast MAC address is created. This property allows to specify that certain bits are fixed. Note that the least significant bit of the first MAC address will always be unset to create a unicast MAC address. If the property is NULL, it is eligible to be overwritten by a default connection setting. If the value is still NULL or an empty string, the default is to create a locally-administered, unicast MAC address. If the value contains one MAC address, this address is used as mask. The set bits of the mask are to be filled with the current MAC address of the device, while the unset bits are subject to randomization. Setting &quot;FE:FF:FF:00:00:00&quot; means to preserve the OUI of the current MAC address and only randomize the lower 3 bytes using the &quot;random&quot; or &quot;stable&quot; algorithm. If the value contains one additional MAC address after the mask, this address is used instead of the current MAC address to fill the bits that shall not be randomized. For example, a value of &quot;FE:FF:FF:00:00:00 68:F7:28:00:00:00&quot; will set the OUI of the MAC address to 68:F7:28, while the lower bits are randomized. A value of &quot;02:00:00:00:00:00 00:00:00:00:00:00&quot; will create a fully scrambled globally-administered, burned-in MAC address. If the value contains more than one additional MAC addresses, one of them is chosen randomly. For example, &quot;02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00&quot; will create a fully scrambled MAC address, randomly locally or globally administered." />
+                  nmcli-description="With &quot;cloned-mac-address&quot; setting &quot;random&quot; or &quot;stable&quot;, by default all bits of the MAC address are scrambled and a locally-administered, unicast MAC address is created. This property allows to specify that certain bits are fixed. Note that the least significant bit of the first MAC address will always be unset to create a unicast MAC address. If the property is NULL, it is eligible to be overwritten by a default connection setting. If the value is still NULL or an empty string, the default is to create a locally-administered, unicast MAC address. If the value contains one MAC address, this address is used as mask. The set bits of the mask are to be filled with the current MAC address of the device, while the unset bits are subject to randomization. Setting &quot;FE:FF:FF:00:00:00&quot; means to preserve the OUI of the current MAC address and only randomize the lower 3 bytes using the &quot;random&quot; or &quot;stable&quot; algorithm. If the value contains one additional MAC address after the mask, this address is used instead of the current MAC address to fill the bits that shall not be randomized. For example, a value of &quot;FE:FF:FF:00:00:00 68:F7:28:00:00:00&quot; will set the OUI of the MAC address to 68:F7:28, while the lower bits are randomized. A value of &quot;02:00:00:00:00:00 00:00:00:00:00:00&quot; will create a fully scrambled globally-administered, burned-in MAC address. If the value contains more than one additional MAC addresses, one of them is chosen randomly. For example, &quot;02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00&quot; will create a fully scrambled MAC address, randomly locally or globally administered." />
         <property name="mac-address-blacklist"
-                  description="A list of permanent MAC addresses of Wi-Fi devices to which this connection should never apply.  Each MAC address should be given in the standard hex-digits-and-colons notation (eg &quot;00:11:22:33:44:55&quot;)." />
+                  nmcli-description="A list of permanent MAC addresses of Wi-Fi devices to which this connection should never apply.  Each MAC address should be given in the standard hex-digits-and-colons notation (eg &quot;00:11:22:33:44:55&quot;)." />
         <property name="mac-address-randomization"
-                  description="One of NM_SETTING_MAC_RANDOMIZATION_DEFAULT (0) (never randomize unless the user has set a global default to randomize and the supplicant supports randomization),  NM_SETTING_MAC_RANDOMIZATION_NEVER (1) (never randomize the MAC address), or NM_SETTING_MAC_RANDOMIZATION_ALWAYS (2) (always randomize the MAC address)." />
+                  nmcli-description="One of NM_SETTING_MAC_RANDOMIZATION_DEFAULT (0) (never randomize unless the user has set a global default to randomize and the supplicant supports randomization),  NM_SETTING_MAC_RANDOMIZATION_NEVER (1) (never randomize the MAC address), or NM_SETTING_MAC_RANDOMIZATION_ALWAYS (2) (always randomize the MAC address)." />
         <property name="mtu"
                   alias="mtu"
-                  description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames." />
+                  nmcli-description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames." />
         <property name="seen-bssids"
-                  description="A list of BSSIDs (each BSSID formatted as a MAC address like &quot;00:11:22:33:44:55&quot;) that have been detected as part of the Wi-Fi network.  NetworkManager internally tracks previously seen BSSIDs. The property is only meant for reading and reflects the BSSID list of NetworkManager. The changes you make to this property will not be preserved." />
+                  nmcli-description="A list of BSSIDs (each BSSID formatted as a MAC address like &quot;00:11:22:33:44:55&quot;) that have been detected as part of the Wi-Fi network.  NetworkManager internally tracks previously seen BSSIDs. The property is only meant for reading and reflects the BSSID list of NetworkManager. The changes you make to this property will not be preserved. This is not a regular property that the user would configure. Instead, NetworkManager automatically sets the seen BSSIDs and tracks them internally in &quot;/var/lib/NetworkManager/seen-bssids&quot; file." />
         <property name="hidden"
-                  description="If TRUE, indicates that the network is a non-broadcasting network that hides its SSID. This works both in infrastructure and AP mode. In infrastructure mode, various workarounds are used for a more reliable discovery of hidden networks, such as probe-scanning the SSID.  However, these workarounds expose inherent insecurities with hidden SSID networks, and thus hidden SSID networks should be used with caution. In AP mode, the created network does not broadcast its SSID. Note that marking the network as hidden may be a privacy issue for you (in infrastructure mode) or client stations (in AP mode), as the explicit probe-scans are distinctly recognizable on the air." />
+                  nmcli-description="If TRUE, indicates that the network is a non-broadcasting network that hides its SSID. This works both in infrastructure and AP mode. In infrastructure mode, various workarounds are used for a more reliable discovery of hidden networks, such as probe-scanning the SSID.  However, these workarounds expose inherent insecurities with hidden SSID networks, and thus hidden SSID networks should be used with caution. In AP mode, the created network does not broadcast its SSID. Note that marking the network as hidden may be a privacy issue for you (in infrastructure mode) or client stations (in AP mode), as the explicit probe-scans are distinctly recognizable on the air." />
         <property name="powersave"
-                  description="One of NM_SETTING_WIRELESS_POWERSAVE_DISABLE (2) (disable Wi-Fi power saving), NM_SETTING_WIRELESS_POWERSAVE_ENABLE (3) (enable Wi-Fi power saving), NM_SETTING_WIRELESS_POWERSAVE_IGNORE (1) (don&apos;t touch currently configure setting) or NM_SETTING_WIRELESS_POWERSAVE_DEFAULT (0) (use the globally configured value). All other values are reserved." />
+                  nmcli-description="One of NM_SETTING_WIRELESS_POWERSAVE_DISABLE (2) (disable Wi-Fi power saving), NM_SETTING_WIRELESS_POWERSAVE_ENABLE (3) (enable Wi-Fi power saving), NM_SETTING_WIRELESS_POWERSAVE_IGNORE (1) (don&apos;t touch currently configure setting) or NM_SETTING_WIRELESS_POWERSAVE_DEFAULT (0) (use the globally configured value). All other values are reserved." />
         <property name="wake-on-wlan"
-                  description="The NMSettingWirelessWakeOnWLan options to enable. Not all devices support all options. May be any combination of NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY (0x2), NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT (0x4), NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC (0x8), NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE (0x10), NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST (0x20), NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE (0x40), NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE (0x80), NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP (0x100) or the special values NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT (0x1) (to use global settings) and NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE (0x8000) (to disable management of Wake-on-LAN in NetworkManager)." />
+                  nmcli-description="The NMSettingWirelessWakeOnWLan options to enable. Not all devices support all options. May be any combination of NM_SETTING_WIRELESS_WAKE_ON_WLAN_ANY (0x2), NM_SETTING_WIRELESS_WAKE_ON_WLAN_DISCONNECT (0x4), NM_SETTING_WIRELESS_WAKE_ON_WLAN_MAGIC (0x8), NM_SETTING_WIRELESS_WAKE_ON_WLAN_GTK_REKEY_FAILURE (0x10), NM_SETTING_WIRELESS_WAKE_ON_WLAN_EAP_IDENTITY_REQUEST (0x20), NM_SETTING_WIRELESS_WAKE_ON_WLAN_4WAY_HANDSHAKE (0x40), NM_SETTING_WIRELESS_WAKE_ON_WLAN_RFKILL_RELEASE (0x80), NM_SETTING_WIRELESS_WAKE_ON_WLAN_TCP (0x100) or the special values NM_SETTING_WIRELESS_WAKE_ON_WLAN_DEFAULT (0x1) (to use global settings) and NM_SETTING_WIRELESS_WAKE_ON_WLAN_IGNORE (0x8000) (to disable management of Wake-on-LAN in NetworkManager)." />
         <property name="ap-isolation"
-                  description="Configures AP isolation, which prevents communication between wireless devices connected to this AP. This property can be set to a value different from NM_TERNARY_DEFAULT (-1) only when the interface is configured in AP mode. If set to NM_TERNARY_TRUE (1), devices are not able to communicate with each other. This increases security because it protects devices against attacks from other clients in the network. At the same time, it prevents devices to access resources on the same wireless networks as file shares, printers, etc. If set to NM_TERNARY_FALSE (0), devices can talk to each other. When set to NM_TERNARY_DEFAULT (-1), the global default is used; in case the global default is unspecified it is assumed to be NM_TERNARY_FALSE (0)." />
+                  nmcli-description="Configures AP isolation, which prevents communication between wireless devices connected to this AP. This property can be set to a value different from NM_TERNARY_DEFAULT (-1) only when the interface is configured in AP mode. If set to NM_TERNARY_TRUE (1), devices are not able to communicate with each other. This increases security because it protects devices against attacks from other clients in the network. At the same time, it prevents devices to access resources on the same wireless networks as file shares, printers, etc. If set to NM_TERNARY_FALSE (0), devices can talk to each other. When set to NM_TERNARY_DEFAULT (-1), the global default is used; in case the global default is unspecified it is assumed to be NM_TERNARY_FALSE (0)." />
     </setting>
     <setting name="802-11-wireless-security"
              alias="wifi-sec" >
         <property name="key-mgmt"
-                  description="Key management used for the connection. One of &quot;none&quot; (WEP or no password protection), &quot;ieee8021x&quot; (Dynamic WEP), &quot;owe&quot; (Opportunistic Wireless Encryption), &quot;wpa-psk&quot; (WPA2 + WPA3 personal), &quot;sae&quot; (WPA3 personal only), &quot;wpa-eap&quot; (WPA2 + WPA3 enterprise) or &quot;wpa-eap-suite-b-192&quot; (WPA3 enterprise only). This property must be set for any Wi-Fi connection that uses security." />
+                  nmcli-description="Key management used for the connection. One of &quot;none&quot; (WEP or no password protection), &quot;ieee8021x&quot; (Dynamic WEP), &quot;owe&quot; (Opportunistic Wireless Encryption), &quot;wpa-psk&quot; (WPA2 + WPA3 personal), &quot;sae&quot; (WPA3 personal only), &quot;wpa-eap&quot; (WPA2 + WPA3 enterprise) or &quot;wpa-eap-suite-b-192&quot; (WPA3 enterprise only). This property must be set for any Wi-Fi connection that uses security." />
         <property name="wep-tx-keyidx"
-                  description="When static WEP is used (ie, key-mgmt = &quot;none&quot;) and a non-default WEP key index is used by the AP, put that WEP key index here.  Valid values are 0 (default key) through 3.  Note that some consumer access points (like the Linksys WRT54G) number the keys 1 - 4." />
+                  nmcli-description="When static WEP is used (ie, key-mgmt = &quot;none&quot;) and a non-default WEP key index is used by the AP, put that WEP key index here.  Valid values are 0 (default key) through 3.  Note that some consumer access points (like the Linksys WRT54G) number the keys 1 - 4." />
         <property name="auth-alg"
-                  description="When WEP is used (ie, key-mgmt = &quot;none&quot; or &quot;ieee8021x&quot;) indicate the 802.11 authentication algorithm required by the AP here.  One of &quot;open&quot; for Open System, &quot;shared&quot; for Shared Key, or &quot;leap&quot; for Cisco LEAP.  When using Cisco LEAP (ie, key-mgmt = &quot;ieee8021x&quot; and auth-alg = &quot;leap&quot;) the &quot;leap-username&quot; and &quot;leap-password&quot; properties must be specified." />
+                  nmcli-description="When WEP is used (ie, key-mgmt = &quot;none&quot; or &quot;ieee8021x&quot;) indicate the 802.11 authentication algorithm required by the AP here.  One of &quot;open&quot; for Open System, &quot;shared&quot; for Shared Key, or &quot;leap&quot; for Cisco LEAP.  When using Cisco LEAP (ie, key-mgmt = &quot;ieee8021x&quot; and auth-alg = &quot;leap&quot;) the &quot;leap-username&quot; and &quot;leap-password&quot; properties must be specified." />
         <property name="proto"
-                  description="List of strings specifying the allowed WPA protocol versions to use. Each element may be one &quot;wpa&quot; (allow WPA) or &quot;rsn&quot; (allow WPA2/RSN).  If not specified, both WPA and RSN connections are allowed." />
+                  nmcli-description="List of strings specifying the allowed WPA protocol versions to use. Each element may be one &quot;wpa&quot; (allow WPA) or &quot;rsn&quot; (allow WPA2/RSN).  If not specified, both WPA and RSN connections are allowed." />
         <property name="pairwise"
-                  description="A list of pairwise encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms in the list. For maximum compatibility leave this property empty.  Each list element may be one of &quot;tkip&quot; or &quot;ccmp&quot;." />
+                  nmcli-description="A list of pairwise encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms in the list. For maximum compatibility leave this property empty.  Each list element may be one of &quot;tkip&quot; or &quot;ccmp&quot;." />
         <property name="group"
-                  description="A list of group/broadcast encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms in the list.  For maximum compatibility leave this property empty.  Each list element may be one of &quot;wep40&quot;, &quot;wep104&quot;, &quot;tkip&quot;, or &quot;ccmp&quot;." />
+                  nmcli-description="A list of group/broadcast encryption algorithms which prevents connections to Wi-Fi networks that do not utilize one of the algorithms in the list.  For maximum compatibility leave this property empty.  Each list element may be one of &quot;wep40&quot;, &quot;wep104&quot;, &quot;tkip&quot;, or &quot;ccmp&quot;." />
         <property name="pmf"
-                  description="Indicates whether Protected Management Frames (802.11w) must be enabled for the connection.  One of NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT (0) (use global default value), NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE (1) (disable PMF), NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL (2) (enable PMF if the supplicant and the access point support it) or NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED (3) (enable PMF and fail if not supported).  When set to NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT (0) and no global default is set, PMF will be optionally enabled." />
+                  nmcli-description="Indicates whether Protected Management Frames (802.11w) must be enabled for the connection.  One of NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT (0) (use global default value), NM_SETTING_WIRELESS_SECURITY_PMF_DISABLE (1) (disable PMF), NM_SETTING_WIRELESS_SECURITY_PMF_OPTIONAL (2) (enable PMF if the supplicant and the access point support it) or NM_SETTING_WIRELESS_SECURITY_PMF_REQUIRED (3) (enable PMF and fail if not supported).  When set to NM_SETTING_WIRELESS_SECURITY_PMF_DEFAULT (0) and no global default is set, PMF will be optionally enabled." />
         <property name="leap-username"
-                  description="The login username for legacy LEAP connections (ie, key-mgmt = &quot;ieee8021x&quot; and auth-alg = &quot;leap&quot;)." />
+                  nmcli-description="The login username for legacy LEAP connections (ie, key-mgmt = &quot;ieee8021x&quot; and auth-alg = &quot;leap&quot;)." />
         <property name="wep-key0"
-                  description="Index 0 WEP key.  This is the WEP key used in most networks.  See the &quot;wep-key-type&quot; property for a description of how this key is interpreted." />
+                  nmcli-description="Index 0 WEP key.  This is the WEP key used in most networks.  See the &quot;wep-key-type&quot; property for a description of how this key is interpreted." />
         <property name="wep-key1"
-                  description="Index 1 WEP key.  This WEP index is not used by most networks.  See the &quot;wep-key-type&quot; property for a description of how this key is interpreted." />
+                  nmcli-description="Index 1 WEP key.  This WEP index is not used by most networks.  See the &quot;wep-key-type&quot; property for a description of how this key is interpreted." />
         <property name="wep-key2"
-                  description="Index 2 WEP key.  This WEP index is not used by most networks.  See the &quot;wep-key-type&quot; property for a description of how this key is interpreted." />
+                  nmcli-description="Index 2 WEP key.  This WEP index is not used by most networks.  See the &quot;wep-key-type&quot; property for a description of how this key is interpreted." />
         <property name="wep-key3"
-                  description="Index 3 WEP key.  This WEP index is not used by most networks.  See the &quot;wep-key-type&quot; property for a description of how this key is interpreted." />
+                  nmcli-description="Index 3 WEP key.  This WEP index is not used by most networks.  See the &quot;wep-key-type&quot; property for a description of how this key is interpreted." />
         <property name="wep-key-flags"
-                  description="Flags indicating how to handle the &quot;wep-key0&quot;, &quot;wep-key1&quot;, &quot;wep-key2&quot;, and &quot;wep-key3&quot; properties." />
+                  nmcli-description="Flags indicating how to handle the &quot;wep-key0&quot;, &quot;wep-key1&quot;, &quot;wep-key2&quot;, and &quot;wep-key3&quot; properties." />
         <property name="wep-key-type"
-                  description="Controls the interpretation of WEP keys.  Allowed values are NM_WEP_KEY_TYPE_KEY (1), in which case the key is either a 10- or 26-character hexadecimal string, or a 5- or 13-character ASCII password; or NM_WEP_KEY_TYPE_PASSPHRASE (2), in which case the passphrase is provided as a string and will be hashed using the de-facto MD5 method to derive the actual WEP key." />
+                  nmcli-description="Controls the interpretation of WEP keys.  Allowed values are NM_WEP_KEY_TYPE_KEY (1), in which case the key is either a 10- or 26-character hexadecimal string, or a 5- or 13-character ASCII password; or NM_WEP_KEY_TYPE_PASSPHRASE (2), in which case the passphrase is provided as a string and will be hashed using the de-facto MD5 method to derive the actual WEP key." />
         <property name="psk"
-                  description="Pre-Shared-Key for WPA networks. For WPA-PSK, it&apos;s either an ASCII passphrase of 8 to 63 characters that is (as specified in the 802.11i standard) hashed to derive the actual key, or the key in form of 64 hexadecimal character. The WPA3-Personal networks use a passphrase of any length for SAE authentication." />
+                  nmcli-description="Pre-Shared-Key for WPA networks. For WPA-PSK, it&apos;s either an ASCII passphrase of 8 to 63 characters that is (as specified in the 802.11i standard) hashed to derive the actual key, or the key in form of 64 hexadecimal character. The WPA3-Personal networks use a passphrase of any length for SAE authentication." />
         <property name="psk-flags"
-                  description="Flags indicating how to handle the &quot;psk&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;psk&quot; property." />
         <property name="leap-password"
-                  description="The login password for legacy LEAP connections (ie, key-mgmt = &quot;ieee8021x&quot; and auth-alg = &quot;leap&quot;)." />
+                  nmcli-description="The login password for legacy LEAP connections (ie, key-mgmt = &quot;ieee8021x&quot; and auth-alg = &quot;leap&quot;)." />
         <property name="leap-password-flags"
-                  description="Flags indicating how to handle the &quot;leap-password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;leap-password&quot; property." />
         <property name="wps-method"
-                  description="Flags indicating which mode of WPS is to be used if any. There&apos;s little point in changing the default setting as NetworkManager will automatically determine whether it&apos;s feasible to start WPS enrollment from the Access Point capabilities. WPS can be disabled by setting this property to a value of 1." />
+                  nmcli-description="Flags indicating which mode of WPS is to be used if any. There&apos;s little point in changing the default setting as NetworkManager will automatically determine whether it&apos;s feasible to start WPS enrollment from the Access Point capabilities. WPS can be disabled by setting this property to a value of 1." />
         <property name="fils"
-                  description="Indicates whether Fast Initial Link Setup (802.11ai) must be enabled for the connection.  One of NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (0) (use global default value), NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE (1) (disable FILS), NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL (2) (enable FILS if the supplicant and the access point support it) or NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED (3) (enable FILS and fail if not supported).  When set to NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (0) and no global default is set, FILS will be optionally enabled." />
+                  nmcli-description="Indicates whether Fast Initial Link Setup (802.11ai) must be enabled for the connection.  One of NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (0) (use global default value), NM_SETTING_WIRELESS_SECURITY_FILS_DISABLE (1) (disable FILS), NM_SETTING_WIRELESS_SECURITY_FILS_OPTIONAL (2) (enable FILS if the supplicant and the access point support it) or NM_SETTING_WIRELESS_SECURITY_FILS_REQUIRED (3) (enable FILS and fail if not supported).  When set to NM_SETTING_WIRELESS_SECURITY_FILS_DEFAULT (0) and no global default is set, FILS will be optionally enabled." />
     </setting>
     <setting name="802-1x" >
         <property name="optional"
-                  description="Whether the 802.1X authentication is optional. If TRUE, the activation will continue even after a timeout or an authentication failure. Setting the property to TRUE is currently allowed only for Ethernet connections. If set to FALSE, the activation can continue only after a successful authentication." />
+                  nmcli-description="Whether the 802.1X authentication is optional. If TRUE, the activation will continue even after a timeout or an authentication failure. Setting the property to TRUE is currently allowed only for Ethernet connections. If set to FALSE, the activation can continue only after a successful authentication." />
         <property name="eap"
-                  description="The allowed EAP method to be used when authenticating to the network with 802.1x.  Valid methods are: &quot;leap&quot;, &quot;md5&quot;, &quot;tls&quot;, &quot;peap&quot;, &quot;ttls&quot;, &quot;pwd&quot;, and &quot;fast&quot;.  Each method requires different configuration using the properties of this setting; refer to wpa_supplicant documentation for the allowed combinations." />
+                  nmcli-description="The allowed EAP method to be used when authenticating to the network with 802.1x.  Valid methods are: &quot;leap&quot;, &quot;md5&quot;, &quot;tls&quot;, &quot;peap&quot;, &quot;ttls&quot;, &quot;pwd&quot;, and &quot;fast&quot;.  Each method requires different configuration using the properties of this setting; refer to wpa_supplicant documentation for the allowed combinations." />
         <property name="identity"
-                  description="Identity string for EAP authentication methods.  Often the user&apos;s user or login name." />
+                  nmcli-description="Identity string for EAP authentication methods.  Often the user&apos;s user or login name." />
         <property name="anonymous-identity"
-                  description="Anonymous identity string for EAP authentication methods.  Used as the unencrypted identity with EAP types that support different tunneled identity like EAP-TTLS." />
+                  nmcli-description="Anonymous identity string for EAP authentication methods.  Used as the unencrypted identity with EAP types that support different tunneled identity like EAP-TTLS." />
         <property name="pac-file"
-                  description="UTF-8 encoded file path containing PAC for EAP-FAST." />
+                  nmcli-description="UTF-8 encoded file path containing PAC for EAP-FAST." />
         <property name="ca-cert"
-                  description="Contains the CA certificate if used by the EAP method specified in the &quot;eap&quot; property. Certificate data is specified using a &quot;scheme&quot;; three are currently supported: blob, path and pkcs#11 URL. When using the blob scheme this property should be set to the certificate&apos;s DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended. Note that enabling NMSetting8021x:system-ca-certs will override this setting to use the built-in path, if the built-in path is not a directory." />
+                  nmcli-description="Contains the CA certificate if used by the EAP method specified in the &quot;eap&quot; property. Certificate data is specified using a &quot;scheme&quot;; three are currently supported: blob, path and pkcs#11 URL. When using the blob scheme this property should be set to the certificate&apos;s DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended. Note that enabling NMSetting8021x:system-ca-certs will override this setting to use the built-in path, if the built-in path is not a directory." />
         <property name="ca-cert-password"
-                  description="The password used to access the CA certificate stored in &quot;ca-cert&quot; property. Only makes sense if the certificate is stored on a PKCS#11 token that requires a login." />
+                  nmcli-description="The password used to access the CA certificate stored in &quot;ca-cert&quot; property. Only makes sense if the certificate is stored on a PKCS#11 token that requires a login." />
         <property name="ca-cert-password-flags"
-                  description="Flags indicating how to handle the &quot;ca-cert-password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;ca-cert-password&quot; property." />
         <property name="ca-path"
-                  description="UTF-8 encoded path to a directory containing PEM or DER formatted certificates to be added to the verification chain in addition to the certificate specified in the &quot;ca-cert&quot; property. If NMSetting8021x:system-ca-certs is enabled and the built-in CA path is an existing directory, then this setting is ignored." />
+                  nmcli-description="UTF-8 encoded path to a directory containing PEM or DER formatted certificates to be added to the verification chain in addition to the certificate specified in the &quot;ca-cert&quot; property. If NMSetting8021x:system-ca-certs is enabled and the built-in CA path is an existing directory, then this setting is ignored." />
         <property name="subject-match"
-                  description="Substring to be matched against the subject of the certificate presented by the authentication server. When unset, no verification of the authentication server certificate&apos;s subject is performed. This property provides little security, if any, and should not be used." />
+                  nmcli-description="Substring to be matched against the subject of the certificate presented by the authentication server. When unset, no verification of the authentication server certificate&apos;s subject is performed. This property provides little security, if any, and should not be used." />
         <property name="altsubject-matches"
-                  description="List of strings to be matched against the altSubjectName of the certificate presented by the authentication server. If the list is empty, no verification of the server certificate&apos;s altSubjectName is performed." />
+                  nmcli-description="List of strings to be matched against the altSubjectName of the certificate presented by the authentication server. If the list is empty, no verification of the server certificate&apos;s altSubjectName is performed." />
         <property name="domain-suffix-match"
-                  description="Constraint for server domain name. If set, this FQDN is used as a suffix match requirement for dNSName element(s) of the certificate presented by the authentication server.  If a matching dNSName is found, this constraint is met.  If no dNSName values are present, this constraint is matched against SubjectName CN using same suffix match comparison. Since version 1.24, multiple valid FQDNs can be passed as a &quot;;&quot; delimited list." />
+                  nmcli-description="Constraint for server domain name. If set, this FQDN is used as a suffix match requirement for dNSName element(s) of the certificate presented by the authentication server.  If a matching dNSName is found, this constraint is met.  If no dNSName values are present, this constraint is matched against SubjectName CN using same suffix match comparison. Since version 1.24, multiple valid FQDNs can be passed as a &quot;;&quot; delimited list." />
         <property name="domain-match"
-                  description="Constraint for server domain name. If set, this list of FQDNs is used as a match requirement for dNSName element(s) of the certificate presented by the authentication server.  If a matching dNSName is found, this constraint is met.  If no dNSName values are present, this constraint is matched against SubjectName CN using the same comparison. Multiple valid FQDNs can be passed as a &quot;;&quot; delimited list." />
+                  nmcli-description="Constraint for server domain name. If set, this list of FQDNs is used as a match requirement for dNSName element(s) of the certificate presented by the authentication server.  If a matching dNSName is found, this constraint is met.  If no dNSName values are present, this constraint is matched against SubjectName CN using the same comparison. Multiple valid FQDNs can be passed as a &quot;;&quot; delimited list." />
         <property name="client-cert"
-                  description="Contains the client certificate if used by the EAP method specified in the &quot;eap&quot; property. Certificate data is specified using a &quot;scheme&quot;; two are currently supported: blob and path. When using the blob scheme (which is backwards compatible with NM 0.7.x) this property should be set to the certificate&apos;s DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte." />
+                  nmcli-description="Contains the client certificate if used by the EAP method specified in the &quot;eap&quot; property. Certificate data is specified using a &quot;scheme&quot;; two are currently supported: blob and path. When using the blob scheme (which is backwards compatible with NM 0.7.x) this property should be set to the certificate&apos;s DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte." />
         <property name="client-cert-password"
-                  description="The password used to access the client certificate stored in &quot;client-cert&quot; property. Only makes sense if the certificate is stored on a PKCS#11 token that requires a login." />
+                  nmcli-description="The password used to access the client certificate stored in &quot;client-cert&quot; property. Only makes sense if the certificate is stored on a PKCS#11 token that requires a login." />
         <property name="client-cert-password-flags"
-                  description="Flags indicating how to handle the &quot;client-cert-password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;client-cert-password&quot; property." />
         <property name="phase1-peapver"
-                  description="Forces which PEAP version is used when PEAP is set as the EAP method in the &quot;eap&quot; property.  When unset, the version reported by the server will be used.  Sometimes when using older RADIUS servers, it is necessary to force the client to use a particular PEAP version.  To do so, this property may be set to &quot;0&quot; or &quot;1&quot; to force that specific PEAP version." />
+                  nmcli-description="Forces which PEAP version is used when PEAP is set as the EAP method in the &quot;eap&quot; property.  When unset, the version reported by the server will be used.  Sometimes when using older RADIUS servers, it is necessary to force the client to use a particular PEAP version.  To do so, this property may be set to &quot;0&quot; or &quot;1&quot; to force that specific PEAP version." />
         <property name="phase1-peaplabel"
-                  description="Forces use of the new PEAP label during key derivation.  Some RADIUS servers may require forcing the new PEAP label to interoperate with PEAPv1.  Set to &quot;1&quot; to force use of the new PEAP label.  See the wpa_supplicant documentation for more details." />
+                  nmcli-description="Forces use of the new PEAP label during key derivation.  Some RADIUS servers may require forcing the new PEAP label to interoperate with PEAPv1.  Set to &quot;1&quot; to force use of the new PEAP label.  See the wpa_supplicant documentation for more details." />
         <property name="phase1-fast-provisioning"
-                  description="Enables or disables in-line provisioning of EAP-FAST credentials when FAST is specified as the EAP method in the &quot;eap&quot; property. Recognized values are &quot;0&quot; (disabled), &quot;1&quot; (allow unauthenticated provisioning), &quot;2&quot; (allow authenticated provisioning), and &quot;3&quot; (allow both authenticated and unauthenticated provisioning).  See the wpa_supplicant documentation for more details." />
+                  nmcli-description="Enables or disables in-line provisioning of EAP-FAST credentials when FAST is specified as the EAP method in the &quot;eap&quot; property. Recognized values are &quot;0&quot; (disabled), &quot;1&quot; (allow unauthenticated provisioning), &quot;2&quot; (allow authenticated provisioning), and &quot;3&quot; (allow both authenticated and unauthenticated provisioning).  See the wpa_supplicant documentation for more details." />
         <property name="phase1-auth-flags"
-                  description="Specifies authentication flags to use in &quot;phase 1&quot; outer authentication using NMSetting8021xAuthFlags options. The individual TLS versions can be explicitly disabled. TLS time checks can be also disabled. If a certain TLS disable flag is not set, it is up to the supplicant to allow or forbid it. The TLS options map to tls_disable_tlsv1_x and tls_disable_time_checks settings. See the wpa_supplicant documentation for more details." />
+                  nmcli-description="Specifies authentication flags to use in &quot;phase 1&quot; outer authentication using NMSetting8021xAuthFlags options. The individual TLS versions can be explicitly disabled. TLS time checks can be also disabled. If a certain TLS disable flag is not set, it is up to the supplicant to allow or forbid it. The TLS options map to tls_disable_tlsv1_x and tls_disable_time_checks settings. See the wpa_supplicant documentation for more details." />
         <property name="phase2-auth"
-                  description="Specifies the allowed &quot;phase 2&quot; inner authentication method when an EAP method that uses an inner TLS tunnel is specified in the &quot;eap&quot; property.  For TTLS this property selects one of the supported non-EAP inner methods: &quot;pap&quot;, &quot;chap&quot;, &quot;mschap&quot;, &quot;mschapv2&quot; while &quot;phase2-autheap&quot; selects an EAP inner method.  For PEAP this selects an inner EAP method, one of: &quot;gtc&quot;, &quot;otp&quot;, &quot;md5&quot; and &quot;tls&quot;. Each &quot;phase 2&quot; inner method requires specific parameters for successful authentication; see the wpa_supplicant documentation for more details. Both &quot;phase2-auth&quot; and &quot;phase2-autheap&quot; cannot be specified." />
+                  nmcli-description="Specifies the allowed &quot;phase 2&quot; inner authentication method when an EAP method that uses an inner TLS tunnel is specified in the &quot;eap&quot; property.  For TTLS this property selects one of the supported non-EAP inner methods: &quot;pap&quot;, &quot;chap&quot;, &quot;mschap&quot;, &quot;mschapv2&quot; while &quot;phase2-autheap&quot; selects an EAP inner method.  For PEAP this selects an inner EAP method, one of: &quot;gtc&quot;, &quot;otp&quot;, &quot;md5&quot; and &quot;tls&quot;. Each &quot;phase 2&quot; inner method requires specific parameters for successful authentication; see the wpa_supplicant documentation for more details. Both &quot;phase2-auth&quot; and &quot;phase2-autheap&quot; cannot be specified." />
         <property name="phase2-autheap"
-                  description="Specifies the allowed &quot;phase 2&quot; inner EAP-based authentication method when TTLS is specified in the &quot;eap&quot; property.  Recognized EAP-based &quot;phase 2&quot; methods are &quot;md5&quot;, &quot;mschapv2&quot;, &quot;otp&quot;, &quot;gtc&quot;, and &quot;tls&quot;. Each &quot;phase 2&quot; inner method requires specific parameters for successful authentication; see the wpa_supplicant documentation for more details." />
+                  nmcli-description="Specifies the allowed &quot;phase 2&quot; inner EAP-based authentication method when TTLS is specified in the &quot;eap&quot; property.  Recognized EAP-based &quot;phase 2&quot; methods are &quot;md5&quot;, &quot;mschapv2&quot;, &quot;otp&quot;, &quot;gtc&quot;, and &quot;tls&quot;. Each &quot;phase 2&quot; inner method requires specific parameters for successful authentication; see the wpa_supplicant documentation for more details." />
         <property name="phase2-ca-cert"
-                  description="Contains the &quot;phase 2&quot; CA certificate if used by the EAP method specified in the &quot;phase2-auth&quot; or &quot;phase2-autheap&quot; properties. Certificate data is specified using a &quot;scheme&quot;; three are currently supported: blob, path and pkcs#11 URL. When using the blob scheme this property should be set to the certificate&apos;s DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended. Note that enabling NMSetting8021x:system-ca-certs will override this setting to use the built-in path, if the built-in path is not a directory." />
+                  nmcli-description="Contains the &quot;phase 2&quot; CA certificate if used by the EAP method specified in the &quot;phase2-auth&quot; or &quot;phase2-autheap&quot; properties. Certificate data is specified using a &quot;scheme&quot;; three are currently supported: blob, path and pkcs#11 URL. When using the blob scheme this property should be set to the certificate&apos;s DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended. Note that enabling NMSetting8021x:system-ca-certs will override this setting to use the built-in path, if the built-in path is not a directory." />
         <property name="phase2-ca-cert-password"
-                  description="The password used to access the &quot;phase2&quot; CA certificate stored in &quot;phase2-ca-cert&quot; property. Only makes sense if the certificate is stored on a PKCS#11 token that requires a login." />
+                  nmcli-description="The password used to access the &quot;phase2&quot; CA certificate stored in &quot;phase2-ca-cert&quot; property. Only makes sense if the certificate is stored on a PKCS#11 token that requires a login." />
         <property name="phase2-ca-cert-password-flags"
-                  description="Flags indicating how to handle the &quot;phase2-ca-cert-password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;phase2-ca-cert-password&quot; property." />
         <property name="phase2-ca-path"
-                  description="UTF-8 encoded path to a directory containing PEM or DER formatted certificates to be added to the verification chain in addition to the certificate specified in the &quot;phase2-ca-cert&quot; property. If NMSetting8021x:system-ca-certs is enabled and the built-in CA path is an existing directory, then this setting is ignored." />
+                  nmcli-description="UTF-8 encoded path to a directory containing PEM or DER formatted certificates to be added to the verification chain in addition to the certificate specified in the &quot;phase2-ca-cert&quot; property. If NMSetting8021x:system-ca-certs is enabled and the built-in CA path is an existing directory, then this setting is ignored." />
         <property name="phase2-subject-match"
-                  description="Substring to be matched against the subject of the certificate presented by the authentication server during the inner &quot;phase 2&quot; authentication. When unset, no verification of the authentication server certificate&apos;s subject is performed. This property provides little security, if any, and should not be used." />
+                  nmcli-description="Substring to be matched against the subject of the certificate presented by the authentication server during the inner &quot;phase 2&quot; authentication. When unset, no verification of the authentication server certificate&apos;s subject is performed. This property provides little security, if any, and should not be used." />
         <property name="phase2-altsubject-matches"
-                  description="List of strings to be matched against the altSubjectName of the certificate presented by the authentication server during the inner &quot;phase 2&quot; authentication. If the list is empty, no verification of the server certificate&apos;s altSubjectName is performed." />
+                  nmcli-description="List of strings to be matched against the altSubjectName of the certificate presented by the authentication server during the inner &quot;phase 2&quot; authentication. If the list is empty, no verification of the server certificate&apos;s altSubjectName is performed." />
         <property name="phase2-domain-suffix-match"
-                  description="Constraint for server domain name. If set, this FQDN is used as a suffix match requirement for dNSName element(s) of the certificate presented by the authentication server during the inner &quot;phase 2&quot; authentication.  If a matching dNSName is found, this constraint is met.  If no dNSName values are present, this constraint is matched against SubjectName CN using same suffix match comparison. Since version 1.24, multiple valid FQDNs can be passed as a &quot;;&quot; delimited list." />
+                  nmcli-description="Constraint for server domain name. If set, this FQDN is used as a suffix match requirement for dNSName element(s) of the certificate presented by the authentication server during the inner &quot;phase 2&quot; authentication.  If a matching dNSName is found, this constraint is met.  If no dNSName values are present, this constraint is matched against SubjectName CN using same suffix match comparison. Since version 1.24, multiple valid FQDNs can be passed as a &quot;;&quot; delimited list." />
         <property name="phase2-domain-match"
-                  description="Constraint for server domain name. If set, this list of FQDNs is used as a match requirement for dNSName element(s) of the certificate presented by the authentication server during the inner &quot;phase 2&quot; authentication. If a matching dNSName is found, this constraint is met.  If no dNSName values are present, this constraint is matched against SubjectName CN using the same comparison. Multiple valid FQDNs can be passed as a &quot;;&quot; delimited list." />
+                  nmcli-description="Constraint for server domain name. If set, this list of FQDNs is used as a match requirement for dNSName element(s) of the certificate presented by the authentication server during the inner &quot;phase 2&quot; authentication. If a matching dNSName is found, this constraint is met.  If no dNSName values are present, this constraint is matched against SubjectName CN using the same comparison. Multiple valid FQDNs can be passed as a &quot;;&quot; delimited list." />
         <property name="phase2-client-cert"
-                  description="Contains the &quot;phase 2&quot; client certificate if used by the EAP method specified in the &quot;phase2-auth&quot; or &quot;phase2-autheap&quot; properties. Certificate data is specified using a &quot;scheme&quot;; two are currently supported: blob and path. When using the blob scheme (which is backwards compatible with NM 0.7.x) this property should be set to the certificate&apos;s DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended." />
+                  nmcli-description="Contains the &quot;phase 2&quot; client certificate if used by the EAP method specified in the &quot;phase2-auth&quot; or &quot;phase2-autheap&quot; properties. Certificate data is specified using a &quot;scheme&quot;; two are currently supported: blob and path. When using the blob scheme (which is backwards compatible with NM 0.7.x) this property should be set to the certificate&apos;s DER encoded data. When using the path scheme, this property should be set to the full UTF-8 encoded path of the certificate, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. This property can be unset even if the EAP method supports CA certificates, but this allows man-in-the-middle attacks and is NOT recommended." />
         <property name="phase2-client-cert-password"
-                  description="The password used to access the &quot;phase2&quot; client certificate stored in &quot;phase2-client-cert&quot; property. Only makes sense if the certificate is stored on a PKCS#11 token that requires a login." />
+                  nmcli-description="The password used to access the &quot;phase2&quot; client certificate stored in &quot;phase2-client-cert&quot; property. Only makes sense if the certificate is stored on a PKCS#11 token that requires a login." />
         <property name="phase2-client-cert-password-flags"
-                  description="Flags indicating how to handle the &quot;phase2-client-cert-password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;phase2-client-cert-password&quot; property." />
         <property name="password"
-                  description="UTF-8 encoded password used for EAP authentication methods. If both the &quot;password&quot; property and the &quot;password-raw&quot; property are specified, &quot;password&quot; is preferred." />
+                  nmcli-description="UTF-8 encoded password used for EAP authentication methods. If both the &quot;password&quot; property and the &quot;password-raw&quot; property are specified, &quot;password&quot; is preferred." />
         <property name="password-flags"
-                  description="Flags indicating how to handle the &quot;password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;password&quot; property." />
         <property name="password-raw"
-                  description="Password used for EAP authentication methods, given as a byte array to allow passwords in other encodings than UTF-8 to be used. If both the &quot;password&quot; property and the &quot;password-raw&quot; property are specified, &quot;password&quot; is preferred." />
+                  nmcli-description="Password used for EAP authentication methods, given as a byte array to allow passwords in other encodings than UTF-8 to be used. If both the &quot;password&quot; property and the &quot;password-raw&quot; property are specified, &quot;password&quot; is preferred." />
         <property name="password-raw-flags"
-                  description="Flags indicating how to handle the &quot;password-raw&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;password-raw&quot; property." />
         <property name="private-key"
-                  description="Contains the private key when the &quot;eap&quot; property is set to &quot;tls&quot;. Key data is specified using a &quot;scheme&quot;; two are currently supported: blob and path. When using the blob scheme and private keys, this property should be set to the key&apos;s encrypted PEM encoded data. When using private keys with the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. When using PKCS#12 format private keys and the blob scheme, this property should be set to the PKCS#12 data and the &quot;private-key-password&quot; property must be set to password used to decrypt the PKCS#12 certificate and key. When using PKCS#12 files and the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte, and as with the blob scheme the &quot;private-key-password&quot; property must be set to the password used to decode the PKCS#12 private key and certificate. WARNING: &quot;private-key&quot; is not a &quot;secret&quot; property, and thus unencrypted private key data using the BLOB scheme may be readable by unprivileged users.  Private keys should always be encrypted with a private key password to prevent unauthorized access to unencrypted private key data." />
+                  nmcli-description="Contains the private key when the &quot;eap&quot; property is set to &quot;tls&quot;. Key data is specified using a &quot;scheme&quot;; two are currently supported: blob and path. When using the blob scheme and private keys, this property should be set to the key&apos;s encrypted PEM encoded data. When using private keys with the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. When using PKCS#12 format private keys and the blob scheme, this property should be set to the PKCS#12 data and the &quot;private-key-password&quot; property must be set to password used to decrypt the PKCS#12 certificate and key. When using PKCS#12 files and the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte, and as with the blob scheme the &quot;private-key-password&quot; property must be set to the password used to decode the PKCS#12 private key and certificate. WARNING: &quot;private-key&quot; is not a &quot;secret&quot; property, and thus unencrypted private key data using the BLOB scheme may be readable by unprivileged users.  Private keys should always be encrypted with a private key password to prevent unauthorized access to unencrypted private key data." />
         <property name="private-key-password"
-                  description="The password used to decrypt the private key specified in the &quot;private-key&quot; property when the private key either uses the path scheme, or if the private key is a PKCS#12 format key." />
+                  nmcli-description="The password used to decrypt the private key specified in the &quot;private-key&quot; property when the private key either uses the path scheme, or if the private key is a PKCS#12 format key." />
         <property name="private-key-password-flags"
-                  description="Flags indicating how to handle the &quot;private-key-password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;private-key-password&quot; property." />
         <property name="phase2-private-key"
-                  description="Contains the &quot;phase 2&quot; inner private key when the &quot;phase2-auth&quot; or &quot;phase2-autheap&quot; property is set to &quot;tls&quot;. Key data is specified using a &quot;scheme&quot;; two are currently supported: blob and path. When using the blob scheme and private keys, this property should be set to the key&apos;s encrypted PEM encoded data. When using private keys with the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. When using PKCS#12 format private keys and the blob scheme, this property should be set to the PKCS#12 data and the &quot;phase2-private-key-password&quot; property must be set to password used to decrypt the PKCS#12 certificate and key. When using PKCS#12 files and the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte, and as with the blob scheme the &quot;phase2-private-key-password&quot; property must be set to the password used to decode the PKCS#12 private key and certificate." />
+                  nmcli-description="Contains the &quot;phase 2&quot; inner private key when the &quot;phase2-auth&quot; or &quot;phase2-autheap&quot; property is set to &quot;tls&quot;. Key data is specified using a &quot;scheme&quot;; two are currently supported: blob and path. When using the blob scheme and private keys, this property should be set to the key&apos;s encrypted PEM encoded data. When using private keys with the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte. When using PKCS#12 format private keys and the blob scheme, this property should be set to the PKCS#12 data and the &quot;phase2-private-key-password&quot; property must be set to password used to decrypt the PKCS#12 certificate and key. When using PKCS#12 files and the path scheme, this property should be set to the full UTF-8 encoded path of the key, prefixed with the string &quot;file://&quot; and ending with a terminating NUL byte, and as with the blob scheme the &quot;phase2-private-key-password&quot; property must be set to the password used to decode the PKCS#12 private key and certificate." />
         <property name="phase2-private-key-password"
-                  description="The password used to decrypt the &quot;phase 2&quot; private key specified in the &quot;phase2-private-key&quot; property when the private key either uses the path scheme, or is a PKCS#12 format key." />
+                  nmcli-description="The password used to decrypt the &quot;phase 2&quot; private key specified in the &quot;phase2-private-key&quot; property when the private key either uses the path scheme, or is a PKCS#12 format key." />
         <property name="phase2-private-key-password-flags"
-                  description="Flags indicating how to handle the &quot;phase2-private-key-password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;phase2-private-key-password&quot; property." />
         <property name="pin"
-                  description="PIN used for EAP authentication methods." />
+                  nmcli-description="PIN used for EAP authentication methods." />
         <property name="pin-flags"
-                  description="Flags indicating how to handle the &quot;pin&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;pin&quot; property." />
         <property name="system-ca-certs"
-                  description="When TRUE, overrides the &quot;ca-path&quot; and &quot;phase2-ca-path&quot; properties using the system CA directory specified at configure time with the --system-ca-path switch.  The certificates in this directory are added to the verification chain in addition to any certificates specified by the &quot;ca-cert&quot; and &quot;phase2-ca-cert&quot; properties. If the path provided with --system-ca-path is rather a file name (bundle of trusted CA certificates), it overrides &quot;ca-cert&quot; and &quot;phase2-ca-cert&quot; properties instead (sets ca_cert/ca_cert2 options for wpa_supplicant)." />
+                  nmcli-description="When TRUE, overrides the &quot;ca-path&quot; and &quot;phase2-ca-path&quot; properties using the system CA directory specified at configure time with the --system-ca-path switch.  The certificates in this directory are added to the verification chain in addition to any certificates specified by the &quot;ca-cert&quot; and &quot;phase2-ca-cert&quot; properties. If the path provided with --system-ca-path is rather a file name (bundle of trusted CA certificates), it overrides &quot;ca-cert&quot; and &quot;phase2-ca-cert&quot; properties instead (sets ca_cert/ca_cert2 options for wpa_supplicant)." />
         <property name="auth-timeout"
-                  description="A timeout for the authentication. Zero means the global default; if the global default is not set, the authentication timeout is 25 seconds." />
+                  nmcli-description="A timeout for the authentication. Zero means the global default; if the global default is not set, the authentication timeout is 25 seconds." />
     </setting>
     <setting name="802-3-ethernet"
              alias="ethernet" >
         <property name="port"
-                  description="Specific port type to use if the device supports multiple attachment methods.  One of &quot;tp&quot; (Twisted Pair), &quot;aui&quot; (Attachment Unit Interface), &quot;bnc&quot; (Thin Ethernet) or &quot;mii&quot; (Media Independent Interface). If the device supports only one port type, this setting is ignored." />
+                  nmcli-description="Specific port type to use if the device supports multiple attachment methods.  One of &quot;tp&quot; (Twisted Pair), &quot;aui&quot; (Attachment Unit Interface), &quot;bnc&quot; (Thin Ethernet) or &quot;mii&quot; (Media Independent Interface). If the device supports only one port type, this setting is ignored." />
         <property name="speed"
-                  description="When a value greater than 0 is set, configures the device to use the specified speed. If &quot;auto-negotiate&quot; is &quot;yes&quot; the specified speed will be the only one advertised during link negotiation: this works only for BASE-T 802.3 specifications and is useful for enforcing gigabit speeds, as in this case link negotiation is mandatory. If the value is unset (0, the default), the link configuration will be either skipped (if &quot;auto-negotiate&quot; is &quot;no&quot;, the default) or will be auto-negotiated (if &quot;auto-negotiate&quot; is &quot;yes&quot;) and the local device will advertise all the supported speeds. In Mbit/s, ie 100 == 100Mbit/s. Must be set together with the &quot;duplex&quot; property when non-zero. Before specifying a speed value be sure your device supports it." />
+                  nmcli-description="When a value greater than 0 is set, configures the device to use the specified speed. If &quot;auto-negotiate&quot; is &quot;yes&quot; the specified speed will be the only one advertised during link negotiation: this works only for BASE-T 802.3 specifications and is useful for enforcing gigabit speeds, as in this case link negotiation is mandatory. If the value is unset (0, the default), the link configuration will be either skipped (if &quot;auto-negotiate&quot; is &quot;no&quot;, the default) or will be auto-negotiated (if &quot;auto-negotiate&quot; is &quot;yes&quot;) and the local device will advertise all the supported speeds. In Mbit/s, ie 100 == 100Mbit/s. Must be set together with the &quot;duplex&quot; property when non-zero. Before specifying a speed value be sure your device supports it." />
         <property name="duplex"
-                  description="When a value is set, either &quot;half&quot; or &quot;full&quot;, configures the device to use the specified duplex mode. If &quot;auto-negotiate&quot; is &quot;yes&quot; the specified duplex mode will be the only one advertised during link negotiation: this works only for BASE-T 802.3 specifications and is useful for enforcing gigabits modes, as in these cases link negotiation is mandatory. If the value is unset (the default), the link configuration will be either skipped (if &quot;auto-negotiate&quot; is &quot;no&quot;, the default) or will be auto-negotiated (if &quot;auto-negotiate&quot; is &quot;yes&quot;) and the local device will advertise all the supported duplex modes. Must be set together with the &quot;speed&quot; property if specified. Before specifying a duplex mode be sure your device supports it." />
+                  nmcli-description="When a value is set, either &quot;half&quot; or &quot;full&quot;, configures the device to use the specified duplex mode. If &quot;auto-negotiate&quot; is &quot;yes&quot; the specified duplex mode will be the only one advertised during link negotiation: this works only for BASE-T 802.3 specifications and is useful for enforcing gigabits modes, as in these cases link negotiation is mandatory. If the value is unset (the default), the link configuration will be either skipped (if &quot;auto-negotiate&quot; is &quot;no&quot;, the default) or will be auto-negotiated (if &quot;auto-negotiate&quot; is &quot;yes&quot;) and the local device will advertise all the supported duplex modes. Must be set together with the &quot;speed&quot; property if specified. Before specifying a duplex mode be sure your device supports it." />
         <property name="auto-negotiate"
-                  description="When TRUE, enforce auto-negotiation of speed and duplex mode. If &quot;speed&quot; and &quot;duplex&quot; properties are both specified, only that single mode will be advertised and accepted during the link auto-negotiation process: this works only for BASE-T 802.3 specifications and is useful for enforcing gigabits modes, as in these cases link negotiation is mandatory. When FALSE, &quot;speed&quot; and &quot;duplex&quot; properties should be both set or link configuration will be skipped." />
+                  nmcli-description="When TRUE, enforce auto-negotiation of speed and duplex mode. If &quot;speed&quot; and &quot;duplex&quot; properties are both specified, only that single mode will be advertised and accepted during the link auto-negotiation process: this works only for BASE-T 802.3 specifications and is useful for enforcing gigabits modes, as in these cases link negotiation is mandatory. When FALSE, &quot;speed&quot; and &quot;duplex&quot; properties should be both set or link configuration will be skipped." />
         <property name="mac-address"
                   alias="mac"
-                  description="If specified, this connection will only apply to the Ethernet device whose permanent MAC address matches. This property does not change the MAC address of the device (i.e. MAC spoofing)." />
+                  nmcli-description="If specified, this connection will only apply to the Ethernet device whose permanent MAC address matches. This property does not change the MAC address of the device (i.e. MAC spoofing)." />
         <property name="cloned-mac-address"
                   alias="cloned-mac"
-                  description="If specified, request that the device use this MAC address instead. This is known as MAC cloning or spoofing. Beside explicitly specifying a MAC address, the special values &quot;preserve&quot;, &quot;permanent&quot;, &quot;random&quot; and &quot;stable&quot; are supported. &quot;preserve&quot; means not to touch the MAC address on activation. &quot;permanent&quot; means to use the permanent hardware address if the device has one (otherwise this is treated as &quot;preserve&quot;). &quot;random&quot; creates a random MAC address on each connect. &quot;stable&quot; creates a hashed MAC address based on connection.stable-id and a machine dependent key. If unspecified, the value can be overwritten via global defaults, see manual of NetworkManager.conf. If still unspecified, it defaults to &quot;preserve&quot; (older versions of NetworkManager may use a different default value). On D-Bus, this field is expressed as &quot;assigned-mac-address&quot; or the deprecated &quot;cloned-mac-address&quot;." />
+                  nmcli-description="If specified, request that the device use this MAC address instead. This is known as MAC cloning or spoofing. Beside explicitly specifying a MAC address, the special values &quot;preserve&quot;, &quot;permanent&quot;, &quot;random&quot; and &quot;stable&quot; are supported. &quot;preserve&quot; means not to touch the MAC address on activation. &quot;permanent&quot; means to use the permanent hardware address if the device has one (otherwise this is treated as &quot;preserve&quot;). &quot;random&quot; creates a random MAC address on each connect. &quot;stable&quot; creates a hashed MAC address based on connection.stable-id and a machine dependent key. If unspecified, the value can be overwritten via global defaults, see manual of NetworkManager.conf. If still unspecified, it defaults to &quot;preserve&quot; (older versions of NetworkManager may use a different default value). On D-Bus, this field is expressed as &quot;assigned-mac-address&quot; or the deprecated &quot;cloned-mac-address&quot;." />
         <property name="generate-mac-address-mask"
-                  description="With &quot;cloned-mac-address&quot; setting &quot;random&quot; or &quot;stable&quot;, by default all bits of the MAC address are scrambled and a locally-administered, unicast MAC address is created. This property allows to specify that certain bits are fixed. Note that the least significant bit of the first MAC address will always be unset to create a unicast MAC address. If the property is NULL, it is eligible to be overwritten by a default connection setting. If the value is still NULL or an empty string, the default is to create a locally-administered, unicast MAC address. If the value contains one MAC address, this address is used as mask. The set bits of the mask are to be filled with the current MAC address of the device, while the unset bits are subject to randomization. Setting &quot;FE:FF:FF:00:00:00&quot; means to preserve the OUI of the current MAC address and only randomize the lower 3 bytes using the &quot;random&quot; or &quot;stable&quot; algorithm. If the value contains one additional MAC address after the mask, this address is used instead of the current MAC address to fill the bits that shall not be randomized. For example, a value of &quot;FE:FF:FF:00:00:00 68:F7:28:00:00:00&quot; will set the OUI of the MAC address to 68:F7:28, while the lower bits are randomized. A value of &quot;02:00:00:00:00:00 00:00:00:00:00:00&quot; will create a fully scrambled globally-administered, burned-in MAC address. If the value contains more than one additional MAC addresses, one of them is chosen randomly. For example, &quot;02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00&quot; will create a fully scrambled MAC address, randomly locally or globally administered." />
+                  nmcli-description="With &quot;cloned-mac-address&quot; setting &quot;random&quot; or &quot;stable&quot;, by default all bits of the MAC address are scrambled and a locally-administered, unicast MAC address is created. This property allows to specify that certain bits are fixed. Note that the least significant bit of the first MAC address will always be unset to create a unicast MAC address. If the property is NULL, it is eligible to be overwritten by a default connection setting. If the value is still NULL or an empty string, the default is to create a locally-administered, unicast MAC address. If the value contains one MAC address, this address is used as mask. The set bits of the mask are to be filled with the current MAC address of the device, while the unset bits are subject to randomization. Setting &quot;FE:FF:FF:00:00:00&quot; means to preserve the OUI of the current MAC address and only randomize the lower 3 bytes using the &quot;random&quot; or &quot;stable&quot; algorithm. If the value contains one additional MAC address after the mask, this address is used instead of the current MAC address to fill the bits that shall not be randomized. For example, a value of &quot;FE:FF:FF:00:00:00 68:F7:28:00:00:00&quot; will set the OUI of the MAC address to 68:F7:28, while the lower bits are randomized. A value of &quot;02:00:00:00:00:00 00:00:00:00:00:00&quot; will create a fully scrambled globally-administered, burned-in MAC address. If the value contains more than one additional MAC addresses, one of them is chosen randomly. For example, &quot;02:00:00:00:00:00 00:00:00:00:00:00 02:00:00:00:00:00&quot; will create a fully scrambled MAC address, randomly locally or globally administered." />
         <property name="mac-address-blacklist"
-                  description="If specified, this connection will never apply to the Ethernet device whose permanent MAC address matches an address in the list.  Each MAC address is in the standard hex-digits-and-colons notation (00:11:22:33:44:55)." />
+                  nmcli-description="If specified, this connection will never apply to the Ethernet device whose permanent MAC address matches an address in the list.  Each MAC address is in the standard hex-digits-and-colons notation (00:11:22:33:44:55)." />
         <property name="mtu"
                   alias="mtu"
-                  description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames." />
+                  nmcli-description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames." />
         <property name="s390-subchannels"
-                  description="Identifies specific subchannels that this network device uses for communication with z/VM or s390 host.  Like the &quot;mac-address&quot; property for non-z/VM devices, this property can be used to ensure this connection only applies to the network device that uses these subchannels.  The list should contain exactly 3 strings, and each string may only be composed of hexadecimal characters and the period (.) character." />
+                  nmcli-description="Identifies specific subchannels that this network device uses for communication with z/VM or s390 host.  Like the &quot;mac-address&quot; property for non-z/VM devices, this property can be used to ensure this connection only applies to the network device that uses these subchannels.  The list should contain exactly 3 strings, and each string may only be composed of hexadecimal characters and the period (.) character." />
         <property name="s390-nettype"
-                  description="s390 network device type; one of &quot;qeth&quot;, &quot;lcs&quot;, or &quot;ctc&quot;, representing the different types of virtual network devices available on s390 systems." />
+                  nmcli-description="s390 network device type; one of &quot;qeth&quot;, &quot;lcs&quot;, or &quot;ctc&quot;, representing the different types of virtual network devices available on s390 systems." />
         <property name="s390-options"
-                  description="Dictionary of key/value pairs of s390-specific device options.  Both keys and values must be strings.  Allowed keys include &quot;portno&quot;, &quot;layer2&quot;, &quot;portname&quot;, &quot;protocol&quot;, among others.  Key names must contain only alphanumeric characters (ie, [a-zA-Z0-9]). Currently, NetworkManager itself does nothing with this information. However, s390utils ships a udev rule which parses this information and applies it to the interface." />
+                  nmcli-description="Dictionary of key/value pairs of s390-specific device options.  Both keys and values must be strings.  Allowed keys include &quot;portno&quot;, &quot;layer2&quot;, &quot;portname&quot;, &quot;protocol&quot;, among others.  Key names must contain only alphanumeric characters (ie, [a-zA-Z0-9]). Currently, NetworkManager itself does nothing with this information. However, s390utils ships a udev rule which parses this information and applies it to the interface." />
         <property name="wake-on-lan"
-                  description="The NMSettingWiredWakeOnLan options to enable. Not all devices support all options. May be any combination of NM_SETTING_WIRED_WAKE_ON_LAN_PHY (0x2), NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST (0x4), NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST (0x8), NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST (0x10), NM_SETTING_WIRED_WAKE_ON_LAN_ARP (0x20), NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC (0x40) or the special values NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT (0x1) (to use global settings) and NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE (0x8000) (to disable management of Wake-on-LAN in NetworkManager)." />
+                  nmcli-description="The NMSettingWiredWakeOnLan options to enable. Not all devices support all options. May be any combination of NM_SETTING_WIRED_WAKE_ON_LAN_PHY (0x2), NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST (0x4), NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST (0x8), NM_SETTING_WIRED_WAKE_ON_LAN_BROADCAST (0x10), NM_SETTING_WIRED_WAKE_ON_LAN_ARP (0x20), NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC (0x40) or the special values NM_SETTING_WIRED_WAKE_ON_LAN_DEFAULT (0x1) (to use global settings) and NM_SETTING_WIRED_WAKE_ON_LAN_IGNORE (0x8000) (to disable management of Wake-on-LAN in NetworkManager)." />
         <property name="wake-on-lan-password"
-                  description="If specified, the password used with magic-packet-based Wake-on-LAN, represented as an Ethernet MAC address.  If NULL, no password will be required." />
+                  nmcli-description="If specified, the password used with magic-packet-based Wake-on-LAN, represented as an Ethernet MAC address.  If NULL, no password will be required." />
         <property name="accept-all-mac-addresses"
-                  description="When TRUE, setup the interface to accept packets for all MAC addresses. This is enabling the kernel interface flag IFF_PROMISC. When FALSE, the interface will only accept the packets with the interface destination mac address or broadcast." />
+                  nmcli-description="When TRUE, setup the interface to accept packets for all MAC addresses. This is enabling the kernel interface flag IFF_PROMISC. When FALSE, the interface will only accept the packets with the interface destination mac address or broadcast." />
     </setting>
     <setting name="adsl" >
         <property name="username"
                   alias="username"
-                  description="Username used to authenticate with the ADSL service." />
+                  nmcli-description="Username used to authenticate with the ADSL service." />
         <property name="password"
                   alias="password"
-                  description="Password used to authenticate with the ADSL service." />
+                  nmcli-description="Password used to authenticate with the ADSL service." />
         <property name="password-flags"
-                  description="Flags indicating how to handle the &quot;password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;password&quot; property." />
         <property name="protocol"
                   alias="protocol"
-                  description="ADSL connection protocol.  Can be &quot;pppoa&quot;, &quot;pppoe&quot; or &quot;ipoatm&quot;." />
+                  nmcli-description="ADSL connection protocol.  Can be &quot;pppoa&quot;, &quot;pppoe&quot; or &quot;ipoatm&quot;." />
         <property name="encapsulation"
                   alias="encapsulation"
-                  description="Encapsulation of ADSL connection.  Can be &quot;vcmux&quot; or &quot;llc&quot;." />
+                  nmcli-description="Encapsulation of ADSL connection.  Can be &quot;vcmux&quot; or &quot;llc&quot;." />
         <property name="vpi"
-                  description="VPI of ADSL connection" />
+                  nmcli-description="VPI of ADSL connection" />
         <property name="vci"
-                  description="VCI of ADSL connection" />
+                  nmcli-description="VCI of ADSL connection" />
     </setting>
     <setting name="bluetooth" >
         <property name="bdaddr"
                   alias="addr"
-                  description="The Bluetooth address of the device." />
+                  nmcli-description="The Bluetooth address of the device." />
         <property name="type"
                   alias="bt-type"
-                  description="Either &quot;dun&quot; for Dial-Up Networking connections or &quot;panu&quot; for Personal Area Networking connections to devices supporting the NAP profile." />
+                  nmcli-description="Either &quot;dun&quot; for Dial-Up Networking connections or &quot;panu&quot; for Personal Area Networking connections to devices supporting the NAP profile." />
     </setting>
     <setting name="bond" >
         <property name="options"
-                  description="Dictionary of key/value pairs of bonding options.  Both keys and values must be strings. Option names must contain only alphanumeric characters (ie, [a-zA-Z0-9])." />
+                  nmcli-description="Dictionary of key/value pairs of bonding options.  Both keys and values must be strings. Option names must contain only alphanumeric characters (ie, [a-zA-Z0-9])." />
     </setting>
     <setting name="bond-port" >
         <property name="queue-id"
                   alias="queue-id"
-                  description="The queue ID of this bond port. The maximum value of queue ID is the number of TX queues currently active in device." />
+                  nmcli-description="The queue ID of this bond port. The maximum value of queue ID is the number of TX queues currently active in device." />
         <property name="prio"
                   alias="prio"
-                  description="The port priority for bond active port re-selection during failover. A higher number means a higher priority in selection. The primary port has the highest priority. This option is only compatible with active-backup, balance-tlb and balance-alb modes." />
+                  nmcli-description="The port priority for bond active port re-selection during failover. A higher number means a higher priority in selection. The primary port has the highest priority. This option is only compatible with active-backup, balance-tlb and balance-alb modes." />
     </setting>
     <setting name="bridge" >
         <property name="mac-address"
                   alias="mac"
-                  description="If specified, the MAC address of bridge. When creating a new bridge, this MAC address will be set. If this field is left unspecified, the &quot;ethernet.cloned-mac-address&quot; is referred instead to generate the initial MAC address. Note that setting &quot;ethernet.cloned-mac-address&quot; anyway overwrites the MAC address of the bridge later while activating the bridge." />
+                  nmcli-description="If specified, the MAC address of bridge. When creating a new bridge, this MAC address will be set. If this field is left unspecified, the &quot;ethernet.cloned-mac-address&quot; is referred instead to generate the initial MAC address. Note that setting &quot;ethernet.cloned-mac-address&quot; anyway overwrites the MAC address of the bridge later while activating the bridge." />
         <property name="stp"
                   alias="stp"
-                  description="Controls whether Spanning Tree Protocol (STP) is enabled for this bridge." />
+                  nmcli-description="Controls whether Spanning Tree Protocol (STP) is enabled for this bridge." />
         <property name="priority"
                   alias="priority"
-                  description="Sets the Spanning Tree Protocol (STP) priority for this bridge.  Lower values are &quot;better&quot;; the lowest priority bridge will be elected the root bridge." />
+                  nmcli-description="Sets the Spanning Tree Protocol (STP) priority for this bridge.  Lower values are &quot;better&quot;; the lowest priority bridge will be elected the root bridge." />
         <property name="forward-delay"
                   alias="forward-delay"
-                  description="The Spanning Tree Protocol (STP) forwarding delay, in seconds." />
+                  nmcli-description="The Spanning Tree Protocol (STP) forwarding delay, in seconds." />
         <property name="hello-time"
                   alias="hello-time"
-                  description="The Spanning Tree Protocol (STP) hello time, in seconds." />
+                  nmcli-description="The Spanning Tree Protocol (STP) hello time, in seconds." />
         <property name="max-age"
                   alias="max-age"
-                  description="The Spanning Tree Protocol (STP) maximum message age, in seconds." />
+                  nmcli-description="The Spanning Tree Protocol (STP) maximum message age, in seconds." />
         <property name="ageing-time"
                   alias="ageing-time"
-                  description="The Ethernet MAC address aging time, in seconds." />
+                  nmcli-description="The Ethernet MAC address aging time, in seconds." />
         <property name="group-address"
-                  description="If specified, The MAC address of the multicast group this bridge uses for STP. The address must be a link-local address in standard Ethernet MAC address format, ie an address of the form 01:80:C2:00:00:0X, with X in [0, 4..F]. If not specified the default value is 01:80:C2:00:00:00." />
+                  nmcli-description="If specified, The MAC address of the multicast group this bridge uses for STP. The address must be a link-local address in standard Ethernet MAC address format, ie an address of the form 01:80:C2:00:00:0X, with X in [0, 4..F]. If not specified the default value is 01:80:C2:00:00:00." />
         <property name="group-forward-mask"
                   alias="group-forward-mask"
-                  description="A mask of group addresses to forward. Usually, group addresses in the range from 01:80:C2:00:00:00 to 01:80:C2:00:00:0F are not forwarded according to standards. This property is a mask of 16 bits, each corresponding to a group address in that range that must be forwarded. The mask can&apos;t have bits 0, 1 or 2 set because they are used for STP, MAC pause frames and LACP." />
+                  nmcli-description="A mask of group addresses to forward. Usually, group addresses in the range from 01:80:C2:00:00:00 to 01:80:C2:00:00:0F are not forwarded according to standards. This property is a mask of 16 bits, each corresponding to a group address in that range that must be forwarded. The mask can&apos;t have bits 0, 1 or 2 set because they are used for STP, MAC pause frames and LACP." />
         <property name="multicast-hash-max"
-                  description="Set maximum size of multicast hash table (value must be a power of 2)." />
+                  nmcli-description="Set maximum size of multicast hash table (value must be a power of 2)." />
         <property name="multicast-last-member-count"
-                  description="Set the number of queries the bridge will send before stopping forwarding a multicast group after a &quot;leave&quot; message has been received." />
+                  nmcli-description="Set the number of queries the bridge will send before stopping forwarding a multicast group after a &quot;leave&quot; message has been received." />
         <property name="multicast-last-member-interval"
-                  description="Set interval (in deciseconds) between queries to find remaining members of a group, after a &quot;leave&quot; message is received." />
+                  nmcli-description="Set interval (in deciseconds) between queries to find remaining members of a group, after a &quot;leave&quot; message is received." />
         <property name="multicast-membership-interval"
-                  description="Set delay (in deciseconds) after which the bridge will leave a group, if no membership reports for this group are received." />
+                  nmcli-description="Set delay (in deciseconds) after which the bridge will leave a group, if no membership reports for this group are received." />
         <property name="multicast-querier"
-                  description="Enable or disable sending of multicast queries by the bridge. If not specified the option is disabled." />
+                  nmcli-description="Enable or disable sending of multicast queries by the bridge. If not specified the option is disabled." />
         <property name="multicast-querier-interval"
-                  description="If no queries are seen after this delay (in deciseconds) has passed, the bridge will start to send its own queries." />
+                  nmcli-description="If no queries are seen after this delay (in deciseconds) has passed, the bridge will start to send its own queries." />
         <property name="multicast-query-interval"
-                  description="Interval (in deciseconds) between queries sent by the bridge after the end of the startup phase." />
+                  nmcli-description="Interval (in deciseconds) between queries sent by the bridge after the end of the startup phase." />
         <property name="multicast-query-response-interval"
-                  description="Set the Max Response Time/Max Response Delay (in deciseconds) for IGMP/MLD queries sent by the bridge." />
+                  nmcli-description="Set the Max Response Time/Max Response Delay (in deciseconds) for IGMP/MLD queries sent by the bridge." />
         <property name="multicast-query-use-ifaddr"
-                  description="If enabled the bridge&apos;s own IP address is used as the source address for IGMP queries otherwise the default of 0.0.0.0 is used." />
+                  nmcli-description="If enabled the bridge&apos;s own IP address is used as the source address for IGMP queries otherwise the default of 0.0.0.0 is used." />
         <property name="multicast-snooping"
                   alias="multicast-snooping"
-                  description="Controls whether IGMP snooping is enabled for this bridge. Note that if snooping was automatically disabled due to hash collisions, the system may refuse to enable the feature until the collisions are resolved." />
+                  nmcli-description="Controls whether IGMP snooping is enabled for this bridge. Note that if snooping was automatically disabled due to hash collisions, the system may refuse to enable the feature until the collisions are resolved." />
         <property name="multicast-startup-query-count"
-                  description="Set the number of IGMP queries to send during startup phase." />
+                  nmcli-description="Set the number of IGMP queries to send during startup phase." />
         <property name="multicast-startup-query-interval"
-                  description="Sets the time (in deciseconds) between queries sent out at startup to determine membership information." />
+                  nmcli-description="Sets the time (in deciseconds) between queries sent out at startup to determine membership information." />
         <property name="multicast-router"
-                  description="Sets bridge&apos;s multicast router. Multicast-snooping must be enabled for this option to work. Supported values are: &apos;auto&apos;, &apos;disabled&apos;, &apos;enabled&apos; to which kernel assigns the numbers 1, 0, and 2, respectively. If not specified the default value is &apos;auto&apos; (1)." />
+                  nmcli-description="Sets bridge&apos;s multicast router. Multicast-snooping must be enabled for this option to work. Supported values are: &apos;auto&apos;, &apos;disabled&apos;, &apos;enabled&apos; to which kernel assigns the numbers 1, 0, and 2, respectively. If not specified the default value is &apos;auto&apos; (1)." />
         <property name="vlan-filtering"
-                  description="Control whether VLAN filtering is enabled on the bridge." />
+                  nmcli-description="Control whether VLAN filtering is enabled on the bridge." />
         <property name="vlan-default-pvid"
-                  description="The default PVID for the ports of the bridge, that is the VLAN id assigned to incoming untagged frames." />
+                  nmcli-description="The default PVID for the ports of the bridge, that is the VLAN id assigned to incoming untagged frames." />
         <property name="vlan-stats-enabled"
-                  description="Controls whether per-VLAN stats accounting is enabled." />
+                  nmcli-description="Controls whether per-VLAN stats accounting is enabled." />
         <property name="vlan-protocol"
-                  description="If specified, the protocol used for VLAN filtering. Supported values are: &apos;802.1Q&apos;, &apos;802.1ad&apos;. If not specified the default value is &apos;802.1Q&apos;." />
+                  nmcli-description="If specified, the protocol used for VLAN filtering. Supported values are: &apos;802.1Q&apos;, &apos;802.1ad&apos;. If not specified the default value is &apos;802.1Q&apos;." />
         <property name="vlans"
-                  description="Array of bridge VLAN objects. In addition to the VLANs specified here, the bridge will also have the default-pvid VLAN configured  by the bridge.vlan-default-pvid property. In nmcli the VLAN list can be specified with the following syntax: $vid [pvid] [untagged] [, $vid [pvid] [untagged]]... where $vid is either a single id between 1 and 4094 or a range, represented as a couple of ids separated by a dash." />
+                  nmcli-description="Array of bridge VLAN objects. In addition to the VLANs specified here, the bridge will also have the default-pvid VLAN configured  by the bridge.vlan-default-pvid property. In nmcli the VLAN list can be specified with the following syntax: $vid [pvid] [untagged] [, $vid [pvid] [untagged]]... where $vid is either a single id between 1 and 4094 or a range, represented as a couple of ids separated by a dash." />
     </setting>
     <setting name="bridge-port" >
         <property name="priority"
                   alias="priority"
-                  description="The Spanning Tree Protocol (STP) priority of this bridge port." />
+                  nmcli-description="The Spanning Tree Protocol (STP) priority of this bridge port." />
         <property name="path-cost"
                   alias="path-cost"
-                  description="The Spanning Tree Protocol (STP) port cost for destinations via this port." />
+                  nmcli-description="The Spanning Tree Protocol (STP) port cost for destinations via this port." />
         <property name="hairpin-mode"
                   alias="hairpin"
-                  description="Enables or disables &quot;hairpin mode&quot; for the port, which allows frames to be sent back out through the port the frame was received on." />
+                  nmcli-description="Enables or disables &quot;hairpin mode&quot; for the port, which allows frames to be sent back out through the port the frame was received on." />
         <property name="vlans"
-                  description="Array of bridge VLAN objects. In addition to the VLANs specified here, the port will also have the default-pvid VLAN configured on the bridge by the bridge.vlan-default-pvid property. In nmcli the VLAN list can be specified with the following syntax: $vid [pvid] [untagged] [, $vid [pvid] [untagged]]... where $vid is either a single id between 1 and 4094 or a range, represented as a couple of ids separated by a dash." />
+                  nmcli-description="Array of bridge VLAN objects. In addition to the VLANs specified here, the port will also have the default-pvid VLAN configured on the bridge by the bridge.vlan-default-pvid property. In nmcli the VLAN list can be specified with the following syntax: $vid [pvid] [untagged] [, $vid [pvid] [untagged]]... where $vid is either a single id between 1 and 4094 or a range, represented as a couple of ids separated by a dash." />
     </setting>
     <setting name="cdma" >
         <property name="number"
-                  description="The number to dial to establish the connection to the CDMA-based mobile broadband network, if any.  If not specified, the default number (#777) is used when required." />
+                  nmcli-description="The number to dial to establish the connection to the CDMA-based mobile broadband network, if any.  If not specified, the default number (#777) is used when required." />
         <property name="username"
                   alias="user"
-                  description="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." />
+                  nmcli-description="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." />
         <property name="password"
                   alias="password"
-                  description="The password used to authenticate with the network, if required.  Many providers do not require a password, or accept any password.  But if a password is required, it is specified here." />
+                  nmcli-description="The password used to authenticate with the network, if required.  Many providers do not require a password, or accept any password.  But if a password is required, it is specified here." />
         <property name="password-flags"
-                  description="Flags indicating how to handle the &quot;password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;password&quot; property." />
         <property name="mtu"
-                  description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames." />
+                  nmcli-description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames." />
     </setting>
     <setting name="connection" >
         <property name="id"
                   alias="con-name"
-                  description="A human readable unique identifier for the connection, like &quot;Work Wi-Fi&quot; or &quot;T-Mobile 3G&quot;." />
+                  nmcli-description="A human readable unique identifier for the connection, like &quot;Work Wi-Fi&quot; or &quot;T-Mobile 3G&quot;." />
         <property name="uuid"
-                  description="The connection.uuid is the real identifier of a profile. It cannot change and it must be unique. It is therefore often best to refer to a profile by UUID, for example with `nmcli connection up uuid $UUID`. The UUID cannot be changed, except in offline mode. In that case, the special values &quot;new&quot;, &quot;generate&quot; and &quot;&quot; are allowed to generate a new random UUID." />
+                  nmcli-description="The connection.uuid is the real identifier of a profile. It cannot change and it must be unique. It is therefore often best to refer to a profile by UUID, for example with `nmcli connection up uuid $UUID`. The UUID cannot be changed, except in offline mode. In that case, the special values &quot;new&quot;, &quot;generate&quot; and &quot;&quot; are allowed to generate a new random UUID." />
         <property name="stable-id"
-                  description="This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used as DHCP client identifier with ipv4.dhcp-client-id=stable and to derive the DHCP DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid]. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device&apos;s name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key. See NetworkManager(8) manual about the secret-key and the host identity. The &apos;$&apos; character is treated special to perform dynamic substitutions at runtime. Currently, supported are &quot;${CONNECTION}&quot;, &quot;${DEVICE}&quot;, &quot;${MAC}&quot;, &quot;${BOOT}&quot;, &quot;${RANDOM}&quot;. These effectively create unique IDs per-connection, per-device, per-boot, or every time. Note that &quot;${DEVICE}&quot; corresponds to the interface name of the device and &quot;${MAC}&quot; is the permanent MAC address of the device. Any unrecognized patterns following &apos;$&apos; are treated verbatim, however are reserved for future use. You are thus advised to avoid &apos;$&apos; or escape it as &quot;$$&quot;. For example, set it to &quot;${CONNECTION}-${BOOT}-${DEVICE}&quot; to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is similar to &quot;${CONNECTION}&quot; and uses a unique, fixed ID for the connection." />
+                  nmcli-description="This represents the identity of the connection used for various purposes. It allows to configure multiple profiles to share the identity. Also, the stable-id can contain placeholders that are substituted dynamically and deterministically depending on the context. The stable-id is used for generating IPv6 stable private addresses with ipv6.addr-gen-mode=stable-privacy. It is also used to seed the generated cloned MAC address for ethernet.cloned-mac-address=stable and wifi.cloned-mac-address=stable. It is also used to derive the DHCP client identifier with ipv4.dhcp-client-id=stable, the DHCPv6 DUID with ipv6.dhcp-duid=stable-[llt,ll,uuid] and the DHCP IAID with ipv4.iaid=stable and ipv6.iaid=stable. Note that depending on the context where it is used, other parameters are also seeded into the generation algorithm. For example, a per-host key is commonly also included, so that different systems end up generating different IDs. Or with ipv6.addr-gen-mode=stable-privacy, also the device&apos;s name is included, so that different interfaces yield different addresses. The per-host key is the identity of your machine and stored in /var/lib/NetworkManager/secret_key. See NetworkManager(8) manual about the secret-key and the host identity. The &apos;$&apos; character is treated special to perform dynamic substitutions at activation time. Currently, supported are &quot;${CONNECTION}&quot;, &quot;${DEVICE}&quot;, &quot;${MAC}&quot;, &quot;${BOOT}&quot;, &quot;${RANDOM}&quot;.  These effectively create unique IDs per-connection, per-device, per-boot, or every time. The &quot;${CONNECTION}&quot; uses the profile&apos;s connection.uuid, the &quot;${DEVICE}&quot; uses the interface name of the device and &quot;${MAC}&quot; the permanent MAC address of the device. Any unrecognized patterns following &apos;$&apos; are treated verbatim, however are reserved for future use. You are thus advised to avoid &apos;$&apos; or escape it as &quot;$$&quot;.  For example, set it to &quot;${CONNECTION}-${BOOT}-${DEVICE}&quot; to create a unique id for this connection that changes with every reboot and differs depending on the interface where the profile activates. If the value is unset, a global connection default is consulted. If the value is still unset, the default is &quot;default${CONNECTION}&quot; go generate an ID unique per connection profile." />
         <property name="type"
                   alias="type"
-                  description="Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, &quot;802-3-ethernet&quot; or &quot;802-11-wireless&quot; or &quot;bluetooth&quot;, etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, &quot;vpn&quot; or &quot;bridge&quot;, etc)." />
+                  nmcli-description="Base type of the connection. For hardware-dependent connections, should contain the setting name of the hardware-type specific setting (ie, &quot;802-3-ethernet&quot; or &quot;802-11-wireless&quot; or &quot;bluetooth&quot;, etc), and for non-hardware dependent connections like VPN or otherwise, should contain the setting name of that setting type (ie, &quot;vpn&quot; or &quot;bridge&quot;, etc)." />
         <property name="interface-name"
                   alias="ifname"
-                  description="The name of the network interface this connection is bound to. If not set, then the connection can be attached to any interface of the appropriate type (subject to restrictions imposed by other settings). For software devices this specifies the name of the created device. For connection types where interface names cannot easily be made persistent (e.g. mobile broadband or USB Ethernet), this property should not be used. Setting this property restricts the interfaces a connection can be used with, and if interface names change or are reordered the connection may be applied to the wrong interface." />
+                  nmcli-description="The name of the network interface this connection is bound to. If not set, then the connection can be attached to any interface of the appropriate type (subject to restrictions imposed by other settings). For software devices this specifies the name of the created device. For connection types where interface names cannot easily be made persistent (e.g. mobile broadband or USB Ethernet), this property should not be used. Setting this property restricts the interfaces a connection can be used with, and if interface names change or are reordered the connection may be applied to the wrong interface." />
         <property name="autoconnect"
                   alias="autoconnect"
-                  description="Whether or not the connection should be automatically connected by NetworkManager when the resources for the connection are available. TRUE to automatically activate the connection, FALSE to require manual intervention to activate the connection. Autoconnect happens when the circumstances are suitable. That means for example that the device is currently managed and not active. Autoconnect thus never replaces or competes with an already active profile. Note that autoconnect is not implemented for VPN profiles. See &quot;secondaries&quot; as an alternative to automatically connect VPN profiles. If multiple profiles are ready to autoconnect on the same device, the one with the better &quot;connection.autoconnect-priority&quot; is chosen. If the priorities are equal, then the most recently connected profile is activated. If the profiles were not connected earlier or their &quot;connection.timestamp&quot; is identical, the choice is undefined. Depending on &quot;connection.multi-connect&quot;, a profile can (auto)connect only once at a time or multiple times." />
+                  nmcli-description="Whether or not the connection should be automatically connected by NetworkManager when the resources for the connection are available. TRUE to automatically activate the connection, FALSE to require manual intervention to activate the connection. Autoconnect happens when the circumstances are suitable. That means for example that the device is currently managed and not active. Autoconnect thus never replaces or competes with an already active profile. Note that autoconnect is not implemented for VPN profiles. See &quot;secondaries&quot; as an alternative to automatically connect VPN profiles. If multiple profiles are ready to autoconnect on the same device, the one with the better &quot;connection.autoconnect-priority&quot; is chosen. If the priorities are equal, then the most recently connected profile is activated. If the profiles were not connected earlier or their &quot;connection.timestamp&quot; is identical, the choice is undefined. Depending on &quot;connection.multi-connect&quot;, a profile can (auto)connect only once at a time or multiple times." />
         <property name="autoconnect-priority"
-                  description="The autoconnect priority in range -999 to 999. If the connection is set to autoconnect, connections with higher priority will be preferred. The higher number means higher priority. Defaults to 0. Note that this property only matters if there are more than one candidate profile to select for autoconnect. In case of equal priority, the profile used most recently is chosen." />
+                  nmcli-description="The autoconnect priority in range -999 to 999. If the connection is set to autoconnect, connections with higher priority will be preferred. The higher number means higher priority. Defaults to 0. Note that this property only matters if there are more than one candidate profile to select for autoconnect. In case of equal priority, the profile used most recently is chosen." />
         <property name="autoconnect-retries"
-                  description="The number of times a connection should be tried when autoactivating before giving up. Zero means forever, -1 means the global default (4 times if not overridden). Setting this to 1 means to try activation only once before blocking autoconnect. Note that after a timeout, NetworkManager will try to autoconnect again." />
+                  nmcli-description="The number of times a connection should be tried when autoactivating before giving up. Zero means forever, -1 means the global default (4 times if not overridden). Setting this to 1 means to try activation only once before blocking autoconnect. Note that after a timeout, NetworkManager will try to autoconnect again." />
         <property name="multi-connect"
-                  description="Specifies whether the profile can be active multiple times at a particular moment. The value is of type NMConnectionMultiConnect." />
+                  nmcli-description="Specifies whether the profile can be active multiple times at a particular moment. The value is of type NMConnectionMultiConnect." />
         <property name="auth-retries"
-                  description="The number of retries for the authentication. Zero means to try indefinitely; -1 means to use a global default. If the global default is not set, the authentication retries for 3 times before failing the connection. Currently, this only applies to 802-1x authentication." />
+                  nmcli-description="The number of retries for the authentication. Zero means to try indefinitely; -1 means to use a global default. If the global default is not set, the authentication retries for 3 times before failing the connection. Currently, this only applies to 802-1x authentication." />
         <property name="timestamp"
-                  description="The time, in seconds since the Unix Epoch, that the connection was last _successfully_ fully activated. NetworkManager updates the connection timestamp periodically when the connection is active to ensure that an active connection has the latest timestamp. The property is only meant for reading (changes to this property will not be preserved)." />
-        <property name="read-only"
-                  description="FALSE if the connection can be modified using the provided settings service&apos;s D-Bus interface with the right privileges, or TRUE if the connection is read-only and cannot be modified." />
+                  nmcli-description="The time, in seconds since the Unix Epoch, that the connection was last _successfully_ fully activated. NetworkManager updates the connection timestamp periodically when the connection is active to ensure that an active connection has the latest timestamp. The property is only meant for reading (changes to this property will not be preserved)." />
         <property name="permissions"
-                  description="An array of strings defining what access a given user has to this connection.  If this is NULL or empty, all users are allowed to access this connection; otherwise users are allowed if and only if they are in this list.  When this is not empty, the connection can be active only when one of the specified users is logged into an active session.  Each entry is of the form &quot;[type]:[id]:[reserved]&quot;; for example, &quot;user:dcbw:blah&quot;. At this time only the &quot;user&quot; [type] is allowed.  Any other values are ignored and reserved for future use.  [id] is the username that this permission refers to, which may not contain the &quot;:&quot; character. Any [reserved] information present must be ignored and is reserved for future use.  All of [type], [id], and [reserved] must be valid UTF-8." />
+                  nmcli-description="An array of strings defining what access a given user has to this connection.  If this is NULL or empty, all users are allowed to access this connection; otherwise users are allowed if and only if they are in this list.  When this is not empty, the connection can be active only when one of the specified users is logged into an active session.  Each entry is of the form &quot;[type]:[id]:[reserved]&quot;; for example, &quot;user:dcbw:blah&quot;. At this time only the &quot;user&quot; [type] is allowed.  Any other values are ignored and reserved for future use.  [id] is the username that this permission refers to, which may not contain the &quot;:&quot; character. Any [reserved] information present must be ignored and is reserved for future use.  All of [type], [id], and [reserved] must be valid UTF-8." />
         <property name="zone"
-                  description="The trust level of a the connection.  Free form case-insensitive string (for example &quot;Home&quot;, &quot;Work&quot;, &quot;Public&quot;).  NULL or unspecified zone means the connection will be placed in the default zone as defined by the firewall. When updating this property on a currently activated connection, the change takes effect immediately." />
+                  nmcli-description="The trust level of a the connection.  Free form case-insensitive string (for example &quot;Home&quot;, &quot;Work&quot;, &quot;Public&quot;).  NULL or unspecified zone means the connection will be placed in the default zone as defined by the firewall. When updating this property on a currently activated connection, the change takes effect immediately." />
         <property name="master"
                   alias="master"
-                  description="Interface name of the master device or UUID of the master connection." />
+                  nmcli-description="Interface name of the master device or UUID of the master connection." />
         <property name="slave-type"
                   alias="slave-type"
-                  description="Setting name of the device type of this slave&apos;s master connection (eg, &quot;bond&quot;), or NULL if this connection is not a slave." />
+                  nmcli-description="Setting name of the device type of this slave&apos;s master connection (eg, &quot;bond&quot;), or NULL if this connection is not a slave." />
         <property name="autoconnect-slaves"
-                  description="Whether or not slaves of this connection should be automatically brought up when NetworkManager activates this connection. This only has a real effect for master connections. The properties &quot;autoconnect&quot;, &quot;autoconnect-priority&quot; and &quot;autoconnect-retries&quot; are unrelated to this setting. The permitted values are: 0: leave slave connections untouched, 1: activate all the slave connections with this connection, -1: default. If -1 (default) is set, global connection.autoconnect-slaves is read to determine the real value. If it is default as well, this fallbacks to 0." />
+                  nmcli-description="Whether or not slaves of this connection should be automatically brought up when NetworkManager activates this connection. This only has a real effect for master connections. The properties &quot;autoconnect&quot;, &quot;autoconnect-priority&quot; and &quot;autoconnect-retries&quot; are unrelated to this setting. The permitted values are: 0: leave slave connections untouched, 1: activate all the slave connections with this connection, -1: default. If -1 (default) is set, global connection.autoconnect-slaves is read to determine the real value. If it is default as well, this fallbacks to 0." />
         <property name="secondaries"
-                  description="List of connection UUIDs that should be activated when the base connection itself is activated. Currently, only VPN connections are supported." />
+                  nmcli-description="List of connection UUIDs that should be activated when the base connection itself is activated. Currently, only VPN connections are supported." />
         <property name="gateway-ping-timeout"
-                  description="If greater than zero, delay success of IP addressing until either the timeout is reached, or an IP gateway replies to a ping." />
+                  nmcli-description="If greater than zero, delay success of IP addressing until either the timeout is reached, or an IP gateway replies to a ping." />
         <property name="metered"
-                  description="Whether the connection is metered. When updating this property on a currently activated connection, the change takes effect immediately." />
+                  nmcli-description="Whether the connection is metered. When updating this property on a currently activated connection, the change takes effect immediately." />
         <property name="lldp"
-                  description="Whether LLDP is enabled for the connection." />
+                  nmcli-description="Whether LLDP is enabled for the connection." />
         <property name="mdns"
-                  description="Whether mDNS is enabled for the connection. The permitted values are: &quot;yes&quot; (2) register hostname and resolving for the connection, &quot;no&quot; (0) disable mDNS for the interface, &quot;resolve&quot; (1) do not register hostname but allow resolving of mDNS host names and &quot;default&quot; (-1) to allow lookup of a global default in NetworkManager.conf. If unspecified, &quot;default&quot; ultimately depends on the DNS plugin (which for systemd-resolved currently means &quot;no&quot;). This feature requires a plugin which supports mDNS. Otherwise, the setting has no effect. One such plugin is dns-systemd-resolved." />
+                  nmcli-description="Whether mDNS is enabled for the connection. The permitted values are: &quot;yes&quot; (2) register hostname and resolving for the connection, &quot;no&quot; (0) disable mDNS for the interface, &quot;resolve&quot; (1) do not register hostname but allow resolving of mDNS host names and &quot;default&quot; (-1) to allow lookup of a global default in NetworkManager.conf. If unspecified, &quot;default&quot; ultimately depends on the DNS plugin (which for systemd-resolved currently means &quot;no&quot;). This feature requires a plugin which supports mDNS. Otherwise, the setting has no effect. One such plugin is dns-systemd-resolved." />
         <property name="llmnr"
-                  description="Whether Link-Local Multicast Name Resolution (LLMNR) is enabled for the connection. LLMNR is a protocol based on the Domain Name System (DNS) packet format that allows both IPv4 and IPv6 hosts to perform name resolution for hosts on the same local link. The permitted values are: &quot;yes&quot; (2) register hostname and resolving for the connection, &quot;no&quot; (0) disable LLMNR for the interface, &quot;resolve&quot; (1) do not register hostname but allow resolving of LLMNR host names If unspecified, &quot;default&quot; ultimately depends on the DNS plugin (which for systemd-resolved currently means &quot;yes&quot;). This feature requires a plugin which supports LLMNR. Otherwise, the setting has no effect. One such plugin is dns-systemd-resolved." />
+                  nmcli-description="Whether Link-Local Multicast Name Resolution (LLMNR) is enabled for the connection. LLMNR is a protocol based on the Domain Name System (DNS) packet format that allows both IPv4 and IPv6 hosts to perform name resolution for hosts on the same local link. The permitted values are: &quot;yes&quot; (2) register hostname and resolving for the connection, &quot;no&quot; (0) disable LLMNR for the interface, &quot;resolve&quot; (1) do not register hostname but allow resolving of LLMNR host names If unspecified, &quot;default&quot; ultimately depends on the DNS plugin (which for systemd-resolved currently means &quot;yes&quot;). This feature requires a plugin which supports LLMNR. Otherwise, the setting has no effect. One such plugin is dns-systemd-resolved." />
         <property name="dns-over-tls"
-                  description="Whether DNSOverTls (dns-over-tls) is enabled for the connection. DNSOverTls is a technology which uses TLS to encrypt dns traffic. The permitted values are: &quot;yes&quot; (2) use DNSOverTls and disabled fallback, &quot;opportunistic&quot; (1) use DNSOverTls but allow fallback to unencrypted resolution, &quot;no&quot; (0) don&apos;t ever use DNSOverTls. If unspecified &quot;default&quot; depends on the plugin used. Systemd-resolved uses global setting. This feature requires a plugin which supports DNSOverTls. Otherwise, the setting has no effect. One such plugin is dns-systemd-resolved." />
+                  nmcli-description="Whether DNSOverTls (dns-over-tls) is enabled for the connection. DNSOverTls is a technology which uses TLS to encrypt dns traffic. The permitted values are: &quot;yes&quot; (2) use DNSOverTls and disabled fallback, &quot;opportunistic&quot; (1) use DNSOverTls but allow fallback to unencrypted resolution, &quot;no&quot; (0) don&apos;t ever use DNSOverTls. If unspecified &quot;default&quot; depends on the plugin used. Systemd-resolved uses global setting. This feature requires a plugin which supports DNSOverTls. Otherwise, the setting has no effect. One such plugin is dns-systemd-resolved." />
         <property name="mptcp-flags"
-                  description="Whether to configure MPTCP endpoints and the address flags. If MPTCP is enabled in NetworkManager, it will configure the addresses of the interface as MPTCP endpoints. Note that IPv4 loopback addresses (127.0.0.0/8), IPv4 link local addresses (169.254.0.0/16), the IPv6 loopback address (::1), IPv6 link local addresses (fe80::/10), IPv6 unique local addresses (ULA, fc00::/7) and IPv6 privacy extension addresses (rfc3041, ipv6.ip6-privacy) will be excluded from being configured as endpoints. If &quot;disabled&quot; (0x1), MPTCP handling for the interface is disabled and no endpoints are registered. The &quot;enabled&quot; (0x2) flag means that MPTCP handling is enabled. This flag can also be implied from the presence of other flags. Even when enabled, MPTCP handling will by default still be disabled unless &quot;/proc/sys/net/mptcp/enabled&quot; sysctl is on. NetworkManager does not change the sysctl and this is up to the administrator or distribution. To configure endpoints even if the sysctl is disabled, &quot;also-without-sysctl&quot; (0x4) flag can be used. In that case, NetworkManager doesn&apos;t look at the sysctl and configures endpoints regardless. Even when enabled, NetworkManager will only configure MPTCP endpoints for a certain address family, if there is a unicast default route (0.0.0.0/0 or ::/0) in the main routing table. The flag &quot;also-without-default-route&quot; (0x8) can override that. When MPTCP handling is enabled then endpoints are configured with the specified address flags &quot;signal&quot; (0x10), &quot;subflow&quot; (0x20), &quot;backup&quot; (0x40), &quot;fullmesh&quot; (0x80). See ip-mptcp(8) manual for additional information about the flags. If the flags are zero (0x0), the global connection default from NetworkManager.conf is honored. If still unspecified, the fallback is &quot;enabled,subflow&quot;. Note that this means that MPTCP is by default done depending on the &quot;/proc/sys/net/mptcp/enabled&quot; sysctl. NetworkManager does not change the MPTCP limits nor enable MPTCP via &quot;/proc/sys/net/mptcp/enabled&quot;. That is a host configuration which the admin can change via sysctl and ip-mptcp. Strict reverse path filtering (rp_filter) breaks many MPTCP use cases, so when MPTCP handling for IPv4 addresses on the interface is enabled, NetworkManager would loosen the strict reverse path filtering (1) to the loose setting (2)." />
+                  nmcli-description="Whether to configure MPTCP endpoints and the address flags. If MPTCP is enabled in NetworkManager, it will configure the addresses of the interface as MPTCP endpoints. Note that IPv4 loopback addresses (127.0.0.0/8), IPv4 link local addresses (169.254.0.0/16), the IPv6 loopback address (::1), IPv6 link local addresses (fe80::/10), IPv6 unique local addresses (ULA, fc00::/7) and IPv6 privacy extension addresses (rfc3041, ipv6.ip6-privacy) will be excluded from being configured as endpoints. If &quot;disabled&quot; (0x1), MPTCP handling for the interface is disabled and no endpoints are registered. The &quot;enabled&quot; (0x2) flag means that MPTCP handling is enabled. This flag can also be implied from the presence of other flags. Even when enabled, MPTCP handling will by default still be disabled unless &quot;/proc/sys/net/mptcp/enabled&quot; sysctl is on. NetworkManager does not change the sysctl and this is up to the administrator or distribution. To configure endpoints even if the sysctl is disabled, &quot;also-without-sysctl&quot; (0x4) flag can be used. In that case, NetworkManager doesn&apos;t look at the sysctl and configures endpoints regardless. Even when enabled, NetworkManager will only configure MPTCP endpoints for a certain address family, if there is a unicast default route (0.0.0.0/0 or ::/0) in the main routing table. The flag &quot;also-without-default-route&quot; (0x8) can override that. When MPTCP handling is enabled then endpoints are configured with the specified address flags &quot;signal&quot; (0x10), &quot;subflow&quot; (0x20), &quot;backup&quot; (0x40), &quot;fullmesh&quot; (0x80). See ip-mptcp(8) manual for additional information about the flags. If the flags are zero (0x0), the global connection default from NetworkManager.conf is honored. If still unspecified, the fallback is &quot;enabled,subflow&quot;. Note that this means that MPTCP is by default done depending on the &quot;/proc/sys/net/mptcp/enabled&quot; sysctl. NetworkManager does not change the MPTCP limits nor enable MPTCP via &quot;/proc/sys/net/mptcp/enabled&quot;. That is a host configuration which the admin can change via sysctl and ip-mptcp. Strict reverse path filtering (rp_filter) breaks many MPTCP use cases, so when MPTCP handling for IPv4 addresses on the interface is enabled, NetworkManager would loosen the strict reverse path filtering (1) to the loose setting (2)." />
         <property name="mud-url"
-                  description="If configured, set to a Manufacturer Usage Description (MUD) URL that points to manufacturer-recommended network policies for IoT devices. It is transmitted as a DHCPv4 or DHCPv6 option. The value must be a valid URL starting with &quot;https://&quot;. The special value &quot;none&quot; is allowed to indicate that no MUD URL is used. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the ultimate default is &quot;none&quot;." />
+                  nmcli-description="If configured, set to a Manufacturer Usage Description (MUD) URL that points to manufacturer-recommended network policies for IoT devices. It is transmitted as a DHCPv4 or DHCPv6 option. The value must be a valid URL starting with &quot;https://&quot;. The special value &quot;none&quot; is allowed to indicate that no MUD URL is used. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the ultimate default is &quot;none&quot;." />
         <property name="wait-device-timeout"
-                  description="Timeout in milliseconds to wait for device at startup. During boot, devices may take a while to be detected by the driver. This property will cause to delay NetworkManager-wait-online.service and nm-online to give the device a chance to appear. This works by waiting for the given timeout until a compatible device for the profile is available and managed. The value 0 means no wait time. The default value is -1, which currently has the same meaning as no wait time." />
+                  nmcli-description="Timeout in milliseconds to wait for device at startup. During boot, devices may take a while to be detected by the driver. This property will cause to delay NetworkManager-wait-online.service and nm-online to give the device a chance to appear. This works by waiting for the given timeout until a compatible device for the profile is available and managed. The value 0 means no wait time. The default value is -1, which currently has the same meaning as no wait time." />
         <property name="wait-activation-delay"
-                  description="Time in milliseconds to wait for connection to be considered activated. The wait will start after the pre-up dispatcher event. The value 0 means no wait time. The default value is -1, which currently has the same meaning as no wait time." />
+                  nmcli-description="Time in milliseconds to wait for connection to be considered activated. The wait will start after the pre-up dispatcher event. The value 0 means no wait time. The default value is -1, which currently has the same meaning as no wait time." />
     </setting>
     <setting name="dcb" >
         <property name="app-fcoe-flags"
-                  description="Specifies the NMSettingDcbFlags for the DCB FCoE application.  Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
+                  nmcli-description="Specifies the NMSettingDcbFlags for the DCB FCoE application.  Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
         <property name="app-fcoe-priority"
-                  description="The highest User Priority (0 - 7) which FCoE frames should use, or -1 for default priority.  Only used when the &quot;app-fcoe-flags&quot; property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." />
+                  nmcli-description="The highest User Priority (0 - 7) which FCoE frames should use, or -1 for default priority.  Only used when the &quot;app-fcoe-flags&quot; property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." />
         <property name="app-fcoe-mode"
-                  description="The FCoE controller mode; either &quot;fabric&quot; or &quot;vn2vn&quot;. Since 1.34, NULL is the default and means &quot;fabric&quot;. Before 1.34, NULL was rejected as invalid and the default was &quot;fabric&quot;." />
+                  nmcli-description="The FCoE controller mode; either &quot;fabric&quot; or &quot;vn2vn&quot;. Since 1.34, NULL is the default and means &quot;fabric&quot;. Before 1.34, NULL was rejected as invalid and the default was &quot;fabric&quot;." />
         <property name="app-iscsi-flags"
-                  description="Specifies the NMSettingDcbFlags for the DCB iSCSI application.  Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
+                  nmcli-description="Specifies the NMSettingDcbFlags for the DCB iSCSI application.  Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
         <property name="app-iscsi-priority"
-                  description="The highest User Priority (0 - 7) which iSCSI frames should use, or -1 for default priority. Only used when the &quot;app-iscsi-flags&quot; property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." />
+                  nmcli-description="The highest User Priority (0 - 7) which iSCSI frames should use, or -1 for default priority. Only used when the &quot;app-iscsi-flags&quot; property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." />
         <property name="app-fip-flags"
-                  description="Specifies the NMSettingDcbFlags for the DCB FIP application.  Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
+                  nmcli-description="Specifies the NMSettingDcbFlags for the DCB FIP application.  Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
         <property name="app-fip-priority"
-                  description="The highest User Priority (0 - 7) which FIP frames should use, or -1 for default priority.  Only used when the &quot;app-fip-flags&quot; property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." />
+                  nmcli-description="The highest User Priority (0 - 7) which FIP frames should use, or -1 for default priority.  Only used when the &quot;app-fip-flags&quot; property includes the NM_SETTING_DCB_FLAG_ENABLE (0x1) flag." />
         <property name="priority-flow-control-flags"
-                  description="Specifies the NMSettingDcbFlags for DCB Priority Flow Control (PFC). Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
+                  nmcli-description="Specifies the NMSettingDcbFlags for DCB Priority Flow Control (PFC). Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
         <property name="priority-flow-control"
-                  description="An array of 8 boolean values, where the array index corresponds to the User Priority (0 - 7) and the value indicates whether or not the corresponding priority should transmit priority pause." />
+                  nmcli-description="An array of 8 boolean values, where the array index corresponds to the User Priority (0 - 7) and the value indicates whether or not the corresponding priority should transmit priority pause." />
         <property name="priority-group-flags"
-                  description="Specifies the NMSettingDcbFlags for DCB Priority Groups.  Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
+                  nmcli-description="Specifies the NMSettingDcbFlags for DCB Priority Groups.  Flags may be any combination of NM_SETTING_DCB_FLAG_ENABLE (0x1), NM_SETTING_DCB_FLAG_ADVERTISE (0x2), and NM_SETTING_DCB_FLAG_WILLING (0x4)." />
         <property name="priority-group-id"
-                  description="An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the Priority Group ID.  Allowed Priority Group ID values are 0 - 7 or 15 for the unrestricted group." />
+                  nmcli-description="An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the Priority Group ID.  Allowed Priority Group ID values are 0 - 7 or 15 for the unrestricted group." />
         <property name="priority-group-bandwidth"
-                  description="An array of 8 uint values, where the array index corresponds to the Priority Group ID (0 - 7) and the value indicates the percentage of link bandwidth allocated to that group.  Allowed values are 0 - 100, and the sum of all values must total 100 percents." />
+                  nmcli-description="An array of 8 uint values, where the array index corresponds to the Priority Group ID (0 - 7) and the value indicates the percentage of link bandwidth allocated to that group.  Allowed values are 0 - 100, and the sum of all values must total 100 percents." />
         <property name="priority-bandwidth"
-                  description="An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the percentage of bandwidth of the priority&apos;s assigned group that the priority may use.  The sum of all percentages for priorities which belong to the same group must total 100 percents." />
+                  nmcli-description="An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the percentage of bandwidth of the priority&apos;s assigned group that the priority may use.  The sum of all percentages for priorities which belong to the same group must total 100 percents." />
         <property name="priority-strict-bandwidth"
-                  description="An array of 8 boolean values, where the array index corresponds to the User Priority (0 - 7) and the value indicates whether or not the priority may use all of the bandwidth allocated to its assigned group." />
+                  nmcli-description="An array of 8 boolean values, where the array index corresponds to the User Priority (0 - 7) and the value indicates whether or not the priority may use all of the bandwidth allocated to its assigned group." />
         <property name="priority-traffic-class"
-                  description="An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the traffic class (0 - 7) to which the priority is mapped." />
+                  nmcli-description="An array of 8 uint values, where the array index corresponds to the User Priority (0 - 7) and the value indicates the traffic class (0 - 7) to which the priority is mapped." />
     </setting>
     <setting name="dummy" >
     </setting>
@@ -547,11 +541,11 @@
         <property name="coalesce-tx-usecs-high" />
         <property name="coalesce-tx-usecs-low" />
         <property name="pause-autoneg"
-                  description="Whether to automatically negotiate on pause frame of flow control mechanism defined by IEEE 802.3x standard." />
+                  nmcli-description="Whether to automatically negotiate on pause frame of flow control mechanism defined by IEEE 802.3x standard." />
         <property name="pause-rx"
-                  description="Whether RX pause should be enabled. Only valid when automatic negotiation is disabled" />
+                  nmcli-description="Whether RX pause should be enabled. Only valid when automatic negotiation is disabled" />
         <property name="pause-tx"
-                  description="Whether TX pause should be enabled. Only valid when automatic negotiation is disabled" />
+                  nmcli-description="Whether TX pause should be enabled. Only valid when automatic negotiation is disabled" />
         <property name="ring-rx" />
         <property name="ring-rx-jumbo" />
         <property name="ring-rx-mini" />
@@ -561,630 +555,646 @@
     </setting>
     <setting name="gsm" >
         <property name="auto-config"
-                  description="When TRUE, the settings such as APN, username, or password will default to values that match the network the modem will register to in the Mobile Broadband Provider database." />
+                  nmcli-description="When TRUE, the settings such as APN, username, or password will default to values that match the network the modem will register to in the Mobile Broadband Provider database." />
         <property name="number"
-                  description="Legacy setting that used to help establishing PPP data sessions for GSM-based modems." />
+                  nmcli-description="Legacy setting that used to help establishing PPP data sessions for GSM-based modems." />
         <property name="username"
                   alias="user"
-                  description="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." />
+                  nmcli-description="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." />
         <property name="password"
                   alias="password"
-                  description="The password used to authenticate with the network, if required.  Many providers do not require a password, or accept any password.  But if a password is required, it is specified here." />
+                  nmcli-description="The password used to authenticate with the network, if required.  Many providers do not require a password, or accept any password.  But if a password is required, it is specified here." />
         <property name="password-flags"
-                  description="Flags indicating how to handle the &quot;password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;password&quot; property." />
         <property name="apn"
                   alias="apn"
-                  description="The GPRS Access Point Name specifying the APN used when establishing a data session with the GSM-based network.  The APN often determines how the user will be billed for their network usage and whether the user has access to the Internet or just a provider-specific walled-garden, so it is important to use the correct APN for the user&apos;s mobile broadband plan. The APN may only be composed of the characters a-z, 0-9, ., and - per GSM 03.60 Section 14.9." />
+                  nmcli-description="The GPRS Access Point Name specifying the APN used when establishing a data session with the GSM-based network.  The APN often determines how the user will be billed for their network usage and whether the user has access to the Internet or just a provider-specific walled-garden, so it is important to use the correct APN for the user&apos;s mobile broadband plan. The APN may only be composed of the characters a-z, 0-9, ., and - per GSM 03.60 Section 14.9." />
         <property name="network-id"
-                  description="The Network ID (GSM LAI format, ie MCC-MNC) to force specific network registration.  If the Network ID is specified, NetworkManager will attempt to force the device to register only on the specified network. This can be used to ensure that the device does not roam when direct roaming control of the device is not otherwise possible." />
+                  nmcli-description="The Network ID (GSM LAI format, ie MCC-MNC) to force specific network registration.  If the Network ID is specified, NetworkManager will attempt to force the device to register only on the specified network. This can be used to ensure that the device does not roam when direct roaming control of the device is not otherwise possible." />
         <property name="pin"
-                  description="If the SIM is locked with a PIN it must be unlocked before any other operations are requested.  Specify the PIN here to allow operation of the device." />
+                  nmcli-description="If the SIM is locked with a PIN it must be unlocked before any other operations are requested.  Specify the PIN here to allow operation of the device." />
         <property name="pin-flags"
-                  description="Flags indicating how to handle the &quot;pin&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;pin&quot; property." />
         <property name="home-only"
-                  description="When TRUE, only connections to the home network will be allowed. Connections to roaming networks will not be made." />
+                  nmcli-description="When TRUE, only connections to the home network will be allowed. Connections to roaming networks will not be made." />
         <property name="device-id"
-                  description="The device unique identifier (as given by the WWAN management service) which this connection applies to.  If given, the connection will only apply to the specified device." />
+                  nmcli-description="The device unique identifier (as given by the WWAN management service) which this connection applies to.  If given, the connection will only apply to the specified device." />
         <property name="sim-id"
-                  description="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 &quot;device-id&quot; which contains a SIM card matching the given identifier." />
+                  nmcli-description="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 &quot;device-id&quot; which contains a SIM card matching the given identifier." />
         <property name="sim-operator-id"
-                  description="A MCC/MNC string like &quot;310260&quot; or &quot;21601&quot; identifying the specific mobile network operator which this connection applies to.  If given, the connection will apply to any device also allowed by &quot;device-id&quot; and &quot;sim-id&quot; which contains a SIM card provisioned by the given operator." />
+                  nmcli-description="A MCC/MNC string like &quot;310260&quot; or &quot;21601&quot; identifying the specific mobile network operator which this connection applies to.  If given, the connection will apply to any device also allowed by &quot;device-id&quot; and &quot;sim-id&quot; which contains a SIM card provisioned by the given operator." />
         <property name="mtu"
-                  description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames." />
+                  nmcli-description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames." />
+        <property name="initial-eps-bearer-configure"
+                  nmcli-description="For LTE modems, this setting determines whether the initial EPS bearer shall be configured when bringing up the connection.  It is inferred TRUE if initial-eps-bearer-apn is set." />
+        <property name="initial-eps-bearer-apn"
+                  nmcli-description="For LTE modems, this sets the APN for the initial EPS bearer that is set up when attaching to the network.  Setting this parameter implies initial-eps-bearer-configure to be TRUE." />
     </setting>
     <setting name="hostname" >
         <property name="priority"
-                  description="The relative priority of this connection to determine the system hostname. A lower numerical value is better (higher priority).  A connection with higher priority is considered before connections with lower priority. If the value is zero, it can be overridden by a global value from NetworkManager configuration. If the property doesn&apos;t have a value in the global configuration, the value is assumed to be 100. Negative values have the special effect of excluding other connections with a greater numerical priority value; so in presence of at least one negative priority, only connections with the lowest priority value will be used to determine the hostname." />
+                  nmcli-description="The relative priority of this connection to determine the system hostname. A lower numerical value is better (higher priority).  A connection with higher priority is considered before connections with lower priority. If the value is zero, it can be overridden by a global value from NetworkManager configuration. If the property doesn&apos;t have a value in the global configuration, the value is assumed to be 100. Negative values have the special effect of excluding other connections with a greater numerical priority value; so in presence of at least one negative priority, only connections with the lowest priority value will be used to determine the hostname." />
         <property name="from-dhcp"
-                  description="Whether the system hostname can be determined from DHCP on this connection. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn&apos;t have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_TRUE (1)." />
+                  nmcli-description="Whether the system hostname can be determined from DHCP on this connection. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn&apos;t have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_TRUE (1)." />
         <property name="from-dns-lookup"
-                  description="Whether the system hostname can be determined from reverse DNS lookup of addresses on this device. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn&apos;t have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_TRUE (1)." />
+                  nmcli-description="Whether the system hostname can be determined from reverse DNS lookup of addresses on this device. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn&apos;t have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_TRUE (1)." />
         <property name="only-from-default"
-                  description="If set to NM_TERNARY_TRUE (1), NetworkManager attempts to get the hostname via DHCPv4/DHCPv6 or reverse DNS lookup on this device only when the device has the default route for the given address family (IPv4/IPv6). If set to NM_TERNARY_FALSE (0), the hostname can be set from this device even if it doesn&apos;t have the default route. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn&apos;t have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_FALSE (0)." />
+                  nmcli-description="If set to NM_TERNARY_TRUE (1), NetworkManager attempts to get the hostname via DHCPv4/DHCPv6 or reverse DNS lookup on this device only when the device has the default route for the given address family (IPv4/IPv6). If set to NM_TERNARY_FALSE (0), the hostname can be set from this device even if it doesn&apos;t have the default route. When set to NM_TERNARY_DEFAULT (-1), the value from global configuration is used. If the property doesn&apos;t have a value in the global configuration, NetworkManager assumes the value to be NM_TERNARY_FALSE (0)." />
     </setting>
     <setting name="infiniband" >
         <property name="mac-address"
                   alias="mac"
-                  description="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)." />
+                  nmcli-description="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)." />
         <property name="mtu"
                   alias="mtu"
-                  description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames." />
+                  nmcli-description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple frames." />
         <property name="transport-mode"
                   alias="transport-mode"
-                  description="The IP-over-InfiniBand transport mode. Either &quot;datagram&quot; or &quot;connected&quot;." />
+                  nmcli-description="The IP-over-InfiniBand transport mode. Either &quot;datagram&quot; or &quot;connected&quot;." />
         <property name="p-key"
                   alias="p-key"
-                  description="The InfiniBand p-key to use for this device. A value of -1 means to use the default p-key (aka &quot;the p-key at index 0&quot;). Otherwise, it is a 16-bit unsigned integer, whose high bit 0x8000 is set if it is a &quot;full membership&quot; p-key. The values 0 and 0x8000 are not allowed. With the p-key set, the interface name is always &quot;$parent.$p_key&quot;. Setting &quot;connection.interface-name&quot; to another name is not supported. Note that kernel will internally always set the full membership bit, although the interface name does not reflect that. Usually the user would want to configure a full membership p-key with 0x8000 flag set." />
+                  nmcli-description="The InfiniBand p-key to use for this device. A value of -1 means to use the default p-key (aka &quot;the p-key at index 0&quot;). Otherwise, it is a 16-bit unsigned integer, whose high bit 0x8000 is set if it is a &quot;full membership&quot; p-key. The values 0 and 0x8000 are not allowed. With the p-key set, the interface name is always &quot;$parent.$p_key&quot;. Setting &quot;connection.interface-name&quot; to another name is not supported. Note that kernel will internally always set the full membership bit, although the interface name does not reflect that. Usually the user would want to configure a full membership p-key with 0x8000 flag set." />
         <property name="parent"
                   alias="parent"
-                  description="The interface name of the parent device of this device. Normally NULL, but if the &quot;p_key&quot; property is set, then you must specify the base device by setting either this property or &quot;mac-address&quot;." />
+                  nmcli-description="The interface name of the parent device of this device. Normally NULL, but if the &quot;p_key&quot; property is set, then you must specify the base device by setting either this property or &quot;mac-address&quot;." />
     </setting>
     <setting name="ip-tunnel" >
         <property name="mode"
                   alias="mode"
-                  description="The tunneling mode, for example NM_IP_TUNNEL_MODE_IPIP (1) or NM_IP_TUNNEL_MODE_GRE (2)." />
+                  nmcli-description="The tunneling mode, for example NM_IP_TUNNEL_MODE_IPIP (1) or NM_IP_TUNNEL_MODE_GRE (2)." />
         <property name="parent"
                   alias="dev"
-                  description="If given, specifies the parent interface name or parent connection UUID the new device will be bound to so that tunneled packets will only be routed via that interface." />
+                  nmcli-description="If given, specifies the parent interface name or parent connection UUID the new device will be bound to so that tunneled packets will only be routed via that interface." />
         <property name="local"
                   alias="local"
-                  description="The local endpoint of the tunnel; the value can be empty, otherwise it must contain an IPv4 or IPv6 address." />
+                  nmcli-description="The local endpoint of the tunnel; the value can be empty, otherwise it must contain an IPv4 or IPv6 address." />
         <property name="remote"
                   alias="remote"
-                  description="The remote endpoint of the tunnel; the value must contain an IPv4 or IPv6 address." />
+                  nmcli-description="The remote endpoint of the tunnel; the value must contain an IPv4 or IPv6 address." />
         <property name="ttl"
-                  description="The TTL to assign to tunneled packets. 0 is a special value meaning that packets inherit the TTL value." />
+                  nmcli-description="The TTL to assign to tunneled packets. 0 is a special value meaning that packets inherit the TTL value." />
         <property name="tos"
-                  description="The type of service (IPv4) or traffic class (IPv6) field to be set on tunneled packets." />
+                  nmcli-description="The type of service (IPv4) or traffic class (IPv6) field to be set on tunneled packets." />
         <property name="path-mtu-discovery"
-                  description="Whether to enable Path MTU Discovery on this tunnel." />
+                  nmcli-description="Whether to enable Path MTU Discovery on this tunnel." />
         <property name="input-key"
-                  description="The key used for tunnel input packets; the property is valid only for certain tunnel modes (GRE, IP6GRE). If empty, no key is used." />
+                  nmcli-description="The key used for tunnel input packets; the property is valid only for certain tunnel modes (GRE, IP6GRE). If empty, no key is used." />
         <property name="output-key"
-                  description="The key used for tunnel output packets; the property is valid only for certain tunnel modes (GRE, IP6GRE). If empty, no key is used." />
+                  nmcli-description="The key used for tunnel output packets; the property is valid only for certain tunnel modes (GRE, IP6GRE). If empty, no key is used." />
         <property name="encapsulation-limit"
-                  description="How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels. To disable this option, add NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1) to ip-tunnel flags." />
+                  nmcli-description="How many additional levels of encapsulation are permitted to be prepended to packets. This property applies only to IPv6 tunnels. To disable this option, add NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1) to ip-tunnel flags." />
         <property name="flow-label"
-                  description="The flow label to assign to tunnel packets. This property applies only to IPv6 tunnels." />
+                  nmcli-description="The flow label to assign to tunnel packets. This property applies only to IPv6 tunnels." />
         <property name="fwmark"
-                  description="The fwmark value to assign to tunnel packets. This property can be set to a non zero value only on VTI and VTI6 tunnels." />
+                  nmcli-description="The fwmark value to assign to tunnel packets. This property can be set to a non zero value only on VTI and VTI6 tunnels." />
         <property name="mtu"
-                  description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple fragments." />
+                  nmcli-description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple fragments." />
         <property name="flags"
-                  description="Tunnel flags. Currently, the following values are supported: NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS (0x2), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL (0x4), NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV (0x8), NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY (0x10), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK (0x20). They are valid only for IPv6 tunnels." />
+                  nmcli-description="Tunnel flags. Currently, the following values are supported: NM_IP_TUNNEL_FLAG_IP6_IGN_ENCAP_LIMIT (0x1), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_TCLASS (0x2), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FLOWLABEL (0x4), NM_IP_TUNNEL_FLAG_IP6_MIP6_DEV (0x8), NM_IP_TUNNEL_FLAG_IP6_RCV_DSCP_COPY (0x10), NM_IP_TUNNEL_FLAG_IP6_USE_ORIG_FWMARK (0x20). They are valid only for IPv6 tunnels." />
     </setting>
     <setting name="ipv4" >
         <property name="method"
-                  description="IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support &quot;disabled&quot;, &quot;auto&quot;, &quot;manual&quot;, and &quot;link-local&quot;. See the subclass-specific documentation for other values. In general, for the &quot;auto&quot; method, properties such as &quot;dns&quot; and &quot;routes&quot; specify information that is added on to the information returned from automatic configuration.  The &quot;ignore-auto-routes&quot; and &quot;ignore-auto-dns&quot; properties modify this behavior. For methods that imply no upstream network, such as &quot;shared&quot; or &quot;link-local&quot;, these properties must be empty. For IPv4 method &quot;shared&quot;, the IP subnet can be configured by adding one manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the shared method must be configured on the interface which shares the internet to a subnet, not on the uplink which is shared." />
+                  nmcli-description="The IPv4 connection method." />
         <property name="dns"
-                  description="Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending &quot;#example.com&quot; to the IP address of the DNS server. This currently only has effect when using systemd-resolved." />
+                  nmcli-description="Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending &quot;#example.com&quot; to the IP address of the DNS server. This currently only has effect when using systemd-resolved." />
         <property name="dns-search"
-                  description="List of DNS search domains. Domains starting with a tilde (&apos;~&apos;) are considered &apos;routing&apos; domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting &quot;ignore-auto-dns&quot;. Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." />
+                  nmcli-description="List of DNS search domains. Domains starting with a tilde (&apos;~&apos;) are considered &apos;routing&apos; domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting &quot;ignore-auto-dns&quot;. Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." />
         <property name="dns-options"
-                  description="Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are &quot;attempts&quot;, &quot;debug&quot;, &quot;edns0&quot;, &quot;inet6&quot;, &quot;ip6-bytestring&quot;, &quot;ip6-dotint&quot;, &quot;ndots&quot;, &quot;no-aaaa&quot;, &quot;no-check-names&quot;, &quot;no-ip6-dotint&quot;, &quot;no-reload&quot;, &quot;no-tld-query&quot;, &quot;rotate&quot;, &quot;single-request&quot;, &quot;single-request-reopen&quot;, &quot;timeout&quot;, &quot;trust-ad&quot;, &quot;use-vc&quot;. The &quot;trust-ad&quot; setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have &quot;trust-ad&quot; enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then &quot;edns0&quot; and &quot;trust-ad&quot; are automatically added." />
+                  nmcli-description="DNS options for /etc/resolv.conf as described in resolv.conf(5) manual. The currently supported options are &quot;attempts&quot;, &quot;debug&quot;, &quot;edns0&quot;, &quot;ndots&quot;, &quot;no-aaaa&quot;, &quot;no-check-names&quot;, &quot;no-reload&quot;, &quot;no-tld-query&quot;, &quot;rotate&quot;, &quot;single-request&quot;, &quot;single-request-reopen&quot;, &quot;timeout&quot;, &quot;trust-ad&quot;, &quot;use-vc&quot;. See the resolv.conf(5) manual. Note that there is a distinction between an unset (default) list and an empty list. In nmcli, to unset the list set the value to &quot;&quot;. To set an empty list, set it to &quot; &quot;. Currently, an unset list has the same meaning as an empty list. That might change in the future. The &quot;trust-ad&quot; setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have &quot;trust-ad&quot; enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then &quot;edns0&quot; and &quot;trust-ad&quot; are automatically added. The valid &quot;ipv4.dns-options&quot; and &quot;ipv6.dns-options&quot; get merged together." />
         <property name="dns-priority"
-                  description="DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the &quot;rotate&quot; option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the &apos;~.&apos; special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured." />
+                  nmcli-description="DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the &quot;rotate&quot; option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the &apos;~.&apos; special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured." />
         <property name="addresses"
                   alias="ip4"
-                  description="A list of IPv4 addresses and their prefix length. Multiple addresses can be separated by comma. For example &quot;192.168.1.5/24, 10.1.0.5/24&quot;. The addresses are listed in decreasing priority, meaning the first address will be the primary address." />
+                  nmcli-description="A list of IPv4 addresses and their prefix length. Multiple addresses can be separated by comma. For example &quot;192.168.1.5/24, 10.1.0.5/24&quot;. The addresses are listed in decreasing priority, meaning the first address will be the primary address." />
         <property name="gateway"
                   alias="gw4"
-                  description="The gateway associated with this configuration. This is only meaningful if &quot;addresses&quot; is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if &quot;never-default&quot; is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See &quot;ip4-auto-default-route&quot;." />
+                  nmcli-description="The gateway associated with this configuration. This is only meaningful if &quot;addresses&quot; is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if &quot;never-default&quot; is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See &quot;ip4-auto-default-route&quot;." />
         <property name="routes"
-                  description="A list of IPv4 destination addresses, prefix length, optional IPv4 next hop addresses, optional route metric, optional attribute. The valid syntax is: &quot;ip[/prefix] [next-hop] [metric] [attribute=val]...[,ip[/prefix]...]&quot;. For example &quot;192.0.2.0/24 10.1.1.1 77, 198.51.100.0/24&quot;." />
+                  nmcli-description="A list of IPv4 destination addresses, prefix length, optional IPv4 next hop addresses, optional route metric, optional attribute. The valid syntax is: &quot;ip[/prefix] [next-hop] [metric] [attribute=val]...[,ip[/prefix]...]&quot;. For example &quot;192.0.2.0/24 10.1.1.1 77, 198.51.100.0/24&quot;." />
         <property name="route-metric"
-                  description="The default metric for routes that don&apos;t explicitly specify a metric. The default value -1 means that the metric is chosen automatically based on the device type. The metric applies to dynamic routes, manual (static) routes that don&apos;t have an explicit metric setting, address prefix routes, and the default route. Note that for IPv6, the kernel accepts zero (0) but coerces it to 1024 (user default). Hence, setting this property to zero effectively mean setting it to 1024. For IPv4, zero is a regular value for the metric." />
+                  nmcli-description="The default metric for routes that don&apos;t explicitly specify a metric. The default value -1 means that the metric is chosen automatically based on the device type. The metric applies to dynamic routes, manual (static) routes that don&apos;t have an explicit metric setting, address prefix routes, and the default route. Note that for IPv6, the kernel accepts zero (0) but coerces it to 1024 (user default). Hence, setting this property to zero effectively mean setting it to 1024. For IPv4, zero is a regular value for the metric." />
         <property name="route-table"
-                  description="Enable policy routing (source routing) and set the routing table used when adding routes. This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes and static routes. But note that static routes can individually overwrite the setting by explicitly specifying a non-zero routing table. If the table setting is left at zero, it is eligible to be overwritten via global configuration. If the property is zero even after applying the global configuration value, policy routing is disabled for the address family of this connection. Policy routing disabled means that NetworkManager will add all routes to the main table (except static routes that explicitly configure a different table). Additionally, NetworkManager will not delete any extraneous routes from tables except the main table. This is to preserve backward compatibility for users who manage routing tables outside of NetworkManager." />
+                  nmcli-description="Enable policy routing (source routing) and set the routing table used when adding routes. This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes and static routes. But note that static routes can individually overwrite the setting by explicitly specifying a non-zero routing table. If the table setting is left at zero, it is eligible to be overwritten via global configuration. If the property is zero even after applying the global configuration value, policy routing is disabled for the address family of this connection. Policy routing disabled means that NetworkManager will add all routes to the main table (except static routes that explicitly configure a different table). Additionally, NetworkManager will not delete any extraneous routes from tables except the main table. This is to preserve backward compatibility for users who manage routing tables outside of NetworkManager." />
         <property name="routing-rules" />
         <property name="replace-local-rule"
-                  description="Connections will default to keep the autogenerated priority 0 local rule unless this setting is set to TRUE." />
+                  nmcli-description="Connections will default to keep the autogenerated priority 0 local rule unless this setting is set to TRUE." />
         <property name="ignore-auto-routes"
-                  description="When &quot;method&quot; is set to &quot;auto&quot; and this property to TRUE, automatically configured routes are ignored and only routes specified in the &quot;routes&quot; property, if any, are used." />
+                  nmcli-description="When &quot;method&quot; is set to &quot;auto&quot; and this property to TRUE, automatically configured routes are ignored and only routes specified in the &quot;routes&quot; property, if any, are used." />
         <property name="ignore-auto-dns"
-                  description="When &quot;method&quot; is set to &quot;auto&quot; and this property to TRUE, automatically configured name servers and search domains are ignored and only name servers and search domains specified in the &quot;dns&quot; and &quot;dns-search&quot; properties, if any, are used." />
+                  nmcli-description="When &quot;method&quot; is set to &quot;auto&quot; and this property to TRUE, automatically configured name servers and search domains are ignored and only name servers and search domains specified in the &quot;dns&quot; and &quot;dns-search&quot; properties, if any, are used." />
         <property name="dhcp-client-id"
-                  description="A string sent to the DHCP server to identify the local machine which the DHCP server may use to customize the DHCP lease and options. When the property is a hex string (&apos;aa:bb:cc&apos;) it is interpreted as a binary client ID, in which case the first byte is assumed to be the &apos;type&apos; field as per RFC 2132 section 9.14 and the remaining bytes may be an hardware address (e.g. &apos;01:xx:xx:xx:xx:xx:xx&apos; where 1 is the Ethernet ARP type and the rest is a MAC address). If the property is not a hex string it is considered as a non-hardware-address client ID and the &apos;type&apos; field is set to 0. The special values &quot;mac&quot; and &quot;perm-mac&quot; are supported, which use the current or permanent MAC address of the device to generate a client identifier with type ethernet (01). Currently, these options only work for ethernet type of links. The special value &quot;ipv6-duid&quot; uses the DUID from &quot;ipv6.dhcp-duid&quot; property as an RFC4361-compliant client identifier. As IAID it uses &quot;ipv4.dhcp-iaid&quot; and falls back to &quot;ipv6.dhcp-iaid&quot; if unset. The special value &quot;duid&quot; generates a RFC4361-compliant client identifier based on &quot;ipv4.dhcp-iaid&quot; and uses a DUID generated by hashing /etc/machine-id. The special value &quot;stable&quot; is supported to generate a type 0 client identifier based on the stable-id (see connection.stable-id) and a per-host key. If you set the stable-id, you may want to include the &quot;${DEVICE}&quot; or &quot;${MAC}&quot; specifier to get a per-device key. If unset, a globally configured default is used. If still unset, the default depends on the DHCP plugin." />
+                  nmcli-description="A string sent to the DHCP server to identify the local machine which the DHCP server may use to customize the DHCP lease and options. When the property is a hex string (&apos;aa:bb:cc&apos;) it is interpreted as a binary client ID, in which case the first byte is assumed to be the &apos;type&apos; field as per RFC 2132 section 9.14 and the remaining bytes may be an hardware address (e.g. &apos;01:xx:xx:xx:xx:xx:xx&apos; where 1 is the Ethernet ARP type and the rest is a MAC address). If the property is not a hex string it is considered as a non-hardware-address client ID and the &apos;type&apos; field is set to 0. The special values &quot;mac&quot; and &quot;perm-mac&quot; are supported, which use the current or permanent MAC address of the device to generate a client identifier with type ethernet (01). Currently, these options only work for ethernet type of links. The special value &quot;ipv6-duid&quot; uses the DUID from &quot;ipv6.dhcp-duid&quot; property as an RFC4361-compliant client identifier. As IAID it uses &quot;ipv4.dhcp-iaid&quot; and falls back to &quot;ipv6.dhcp-iaid&quot; if unset. The special value &quot;duid&quot; generates a RFC4361-compliant client identifier based on &quot;ipv4.dhcp-iaid&quot; and uses a DUID generated by hashing /etc/machine-id. The special value &quot;stable&quot; is supported to generate a type 0 client identifier based on the stable-id (see connection.stable-id) and a per-host key. If you set the stable-id, you may want to include the &quot;${DEVICE}&quot; or &quot;${MAC}&quot; specifier to get a per-device key. If unset, a globally configured default is used. If still unset, the default depends on the DHCP plugin." />
         <property name="dhcp-iaid"
-                  description="A string containing the &quot;Identity Association Identifier&quot; (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or or as colon separated hexadecimal numbers). Alternatively it can be set to the special values &quot;mac&quot;, &quot;perm-mac&quot;, &quot;ifname&quot; or &quot;stable&quot;. When set to &quot;mac&quot; (or &quot;perm-mac&quot;), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to &quot;ifname&quot;, the IAID is computed by hashing the interface name. The special value &quot;stable&quot; can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be &quot;ifname&quot;. For DHCPv4, the IAID is only used with &quot;ipv4.dhcp-client-id&quot; values &quot;duid&quot; and &quot;ipv6-duid&quot; to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the &quot;internal&quot; DHCPv6 plugin. The &quot;dhclient&quot; DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device." />
+                  nmcli-description="A string containing the &quot;Identity Association Identifier&quot; (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or as colon separated hexadecimal numbers). Alternatively it can be set to the special values &quot;mac&quot;, &quot;perm-mac&quot;, &quot;ifname&quot; or &quot;stable&quot;. When set to &quot;mac&quot; (or &quot;perm-mac&quot;), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to &quot;ifname&quot;, the IAID is computed by hashing the interface name. The special value &quot;stable&quot; can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be &quot;ifname&quot;. For DHCPv4, the IAID is only used with &quot;ipv4.dhcp-client-id&quot; values &quot;duid&quot; and &quot;ipv6-duid&quot; to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the &quot;internal&quot; DHCPv6 plugin. The &quot;dhclient&quot; DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device." />
         <property name="dhcp-timeout"
-                  description="A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity." />
+                  nmcli-description="A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity." />
         <property name="dhcp-send-hostname"
-                  description="If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the &quot;dhcp-hostname&quot; property is NULL and this property is TRUE, the current persistent hostname of the computer is sent." />
+                  nmcli-description="If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the &quot;dhcp-hostname&quot; property is NULL and this property is TRUE, the current persistent hostname of the computer is sent." />
         <property name="dhcp-hostname"
-                  description="If the &quot;dhcp-send-hostname&quot; property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and &quot;dhcp-fqdn&quot; are mutually exclusive and cannot be set at the same time." />
+                  nmcli-description="If the &quot;dhcp-send-hostname&quot; property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and &quot;dhcp-fqdn&quot; are mutually exclusive and cannot be set at the same time." />
         <property name="dhcp-fqdn"
-                  description="If the &quot;dhcp-send-hostname&quot; property is TRUE, then the specified FQDN will be sent to the DHCP server when acquiring a lease. This property and &quot;dhcp-hostname&quot; are mutually exclusive and cannot be set at the same time." />
+                  nmcli-description="If the &quot;dhcp-send-hostname&quot; property is TRUE, then the specified FQDN will be sent to the DHCP server when acquiring a lease. This property and &quot;dhcp-hostname&quot; are mutually exclusive and cannot be set at the same time." />
         <property name="dhcp-hostname-flags"
-                  description="Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) and NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE (0x4).  When no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is not set, the standard FQDN flags are set in the request: NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) for IPv4 and NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1) for IPv6. When this property is set to the default value NM_DHCP_HOSTNAME_FLAG_NONE (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also NM_DHCP_HOSTNAME_FLAG_NONE (0x0), then the standard FQDN flags described above are sent in the DHCP requests." />
+                  nmcli-description="Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) and NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE (0x4).  When no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is not set, the standard FQDN flags are set in the request: NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) for IPv4 and NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1) for IPv6. When this property is set to the default value NM_DHCP_HOSTNAME_FLAG_NONE (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also NM_DHCP_HOSTNAME_FLAG_NONE (0x0), then the standard FQDN flags described above are sent in the DHCP requests." />
         <property name="never-default"
-                  description="If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager." />
+                  nmcli-description="If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager." />
         <property name="may-fail"
-                  description="If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out.  Note that at least one IP configuration must succeed or overall network configuration will still fail.  For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully." />
+                  nmcli-description="If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out.  Note that at least one IP configuration must succeed or overall network configuration will still fail.  For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully." />
         <property name="required-timeout"
-                  description="The minimum time interval in milliseconds for which dynamic IP configuration should be tried before the connection succeeds. This property is useful for example if both IPv4 and IPv6 are enabled and are allowed to fail. Normally the connection succeeds as soon as one of the two address families completes; by setting a required timeout for e.g. IPv4, one can ensure that even if IP6 succeeds earlier than IPv4, NetworkManager waits some time for IPv4 before the connection becomes active. Note that if &quot;may-fail&quot; is FALSE for the same address family, this property has no effect as NetworkManager needs to wait for the full DHCP timeout. A zero value means that no required timeout is present, -1 means the default value (either configuration ipvx.required-timeout override or zero)." />
+                  nmcli-description="The minimum time interval in milliseconds for which dynamic IP configuration should be tried before the connection succeeds. This property is useful for example if both IPv4 and IPv6 are enabled and are allowed to fail. Normally the connection succeeds as soon as one of the two address families completes; by setting a required timeout for e.g. IPv4, one can ensure that even if IP6 succeeds earlier than IPv4, NetworkManager waits some time for IPv4 before the connection becomes active. Note that if &quot;may-fail&quot; is FALSE for the same address family, this property has no effect as NetworkManager needs to wait for the full DHCP timeout. A zero value means that no required timeout is present, -1 means the default value (either configuration ipvx.required-timeout override or zero)." />
         <property name="dad-timeout"
-                  description="Timeout in milliseconds used to check for the presence of duplicate IP addresses on the network.  If an address conflict is detected, the activation will fail.  A zero value means that no duplicate address detection is performed, -1 means the default value (either configuration ipvx.dad-timeout override or zero).  A value greater than zero is a timeout in milliseconds. The property is currently implemented only for IPv4." />
+                  nmcli-description="Timeout in milliseconds used to check for the presence of duplicate IP addresses on the network.  If an address conflict is detected, the activation will fail.  A zero value means that no duplicate address detection is performed, -1 means the default value (either configuration ipvx.dad-timeout override or zero).  A value greater than zero is a timeout in milliseconds. The property is currently implemented only for IPv4." />
         <property name="dhcp-vendor-class-identifier"
-                  description="The Vendor Class Identifier DHCP option (60). Special characters in the data string may be escaped using C-style escapes, nevertheless this property cannot contain nul bytes. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the DHCP option is not sent to the server." />
+                  nmcli-description="The Vendor Class Identifier DHCP option (60). Special characters in the data string may be escaped using C-style escapes, nevertheless this property cannot contain nul bytes. If the per-profile value is unspecified (the default), a global connection default gets consulted. If still unspecified, the DHCP option is not sent to the server." />
         <property name="link-local"
-                  description="Enable and disable the IPv4 link-local configuration independently of the ipv4.method configuration. This allows a link-local address (169.254.x.y/16) to be obtained in addition to other addresses, such as those manually configured or obtained from a DHCP server. When set to &quot;auto&quot;, the value is dependent on &quot;ipv4.method&quot;. When set to &quot;default&quot;, it honors the global connection default, before falling back to &quot;auto&quot;. Note that if &quot;ipv4.method&quot; is &quot;disabled&quot;, then link local addressing is always disabled too. The default is &quot;default&quot;." />
+                  nmcli-description="Enable and disable the IPv4 link-local configuration independently of the ipv4.method configuration. This allows a link-local address (169.254.x.y/16) to be obtained in addition to other addresses, such as those manually configured or obtained from a DHCP server. When set to &quot;auto&quot;, the value is dependent on &quot;ipv4.method&quot;. When set to &quot;default&quot;, it honors the global connection default, before falling back to &quot;auto&quot;. Note that if &quot;ipv4.method&quot; is &quot;disabled&quot;, then link local addressing is always disabled too. The default is &quot;default&quot;." />
         <property name="dhcp-reject-servers"
-                  description="Array of servers from which DHCP offers must be rejected. This property is useful to avoid getting a lease from misconfigured or rogue servers. For DHCPv4, each element must be an IPv4 address, optionally followed by a slash and a prefix length (e.g. &quot;192.168.122.0/24&quot;). This property is currently not implemented for DHCPv6." />
+                  nmcli-description="Array of servers from which DHCP offers must be rejected. This property is useful to avoid getting a lease from misconfigured or rogue servers. For DHCPv4, each element must be an IPv4 address, optionally followed by a slash and a prefix length (e.g. &quot;192.168.122.0/24&quot;). This property is currently not implemented for DHCPv6." />
         <property name="auto-route-ext-gw"
-                  description="VPN connections will default to add the route automatically unless this setting is set to FALSE. For other connection types, adding such an automatic route is currently not supported and setting this to TRUE has no effect." />
+                  nmcli-description="VPN connections will default to add the route automatically unless this setting is set to FALSE. For other connection types, adding such an automatic route is currently not supported and setting this to TRUE has no effect." />
     </setting>
     <setting name="ipv6" >
         <property name="method"
-                  description="IP configuration method. NMSettingIP4Config and NMSettingIP6Config both support &quot;disabled&quot;, &quot;auto&quot;, &quot;manual&quot;, and &quot;link-local&quot;. See the subclass-specific documentation for other values. In general, for the &quot;auto&quot; method, properties such as &quot;dns&quot; and &quot;routes&quot; specify information that is added on to the information returned from automatic configuration.  The &quot;ignore-auto-routes&quot; and &quot;ignore-auto-dns&quot; properties modify this behavior. For methods that imply no upstream network, such as &quot;shared&quot; or &quot;link-local&quot;, these properties must be empty. For IPv4 method &quot;shared&quot;, the IP subnet can be configured by adding one manual IPv4 address or otherwise 10.42.x.0/24 is chosen. Note that the shared method must be configured on the interface which shares the internet to a subnet, not on the uplink which is shared." />
+                  nmcli-description="The IPv6 connection method." />
         <property name="dns"
-                  description="Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending &quot;#example.com&quot; to the IP address of the DNS server. This currently only has effect when using systemd-resolved." />
+                  nmcli-description="Array of IP addresses of DNS servers. For DoT (DNS over TLS), the SNI server name can be specified by appending &quot;#example.com&quot; to the IP address of the DNS server. This currently only has effect when using systemd-resolved." />
         <property name="dns-search"
-                  description="List of DNS search domains. Domains starting with a tilde (&apos;~&apos;) are considered &apos;routing&apos; domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting &quot;ignore-auto-dns&quot;. Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." />
+                  nmcli-description="List of DNS search domains. Domains starting with a tilde (&apos;~&apos;) are considered &apos;routing&apos; domains and are used only to decide the interface over which a query must be forwarded; they are not used to complete unqualified host names. When using a DNS plugin that supports Conditional Forwarding or Split DNS, then the search domains specify which name servers to query. This makes the behavior different from running with plain /etc/resolv.conf. For more information see also the dns-priority setting. When set on a profile that also enabled DHCP, the DNS search list received automatically (option 119 for DHCPv4 and option 24 for DHCPv6) gets merged with the manual list. This can be prevented by setting &quot;ignore-auto-dns&quot;. Note that if no DNS searches are configured, the fallback will be derived from the domain from DHCP (option 15)." />
         <property name="dns-options"
-                  description="Array of DNS options as described in man 5 resolv.conf. NULL means that the options are unset and left at the default. In this case NetworkManager will use default options. This is distinct from an empty list of properties. The currently supported options are &quot;attempts&quot;, &quot;debug&quot;, &quot;edns0&quot;, &quot;inet6&quot;, &quot;ip6-bytestring&quot;, &quot;ip6-dotint&quot;, &quot;ndots&quot;, &quot;no-aaaa&quot;, &quot;no-check-names&quot;, &quot;no-ip6-dotint&quot;, &quot;no-reload&quot;, &quot;no-tld-query&quot;, &quot;rotate&quot;, &quot;single-request&quot;, &quot;single-request-reopen&quot;, &quot;timeout&quot;, &quot;trust-ad&quot;, &quot;use-vc&quot;. The &quot;trust-ad&quot; setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have &quot;trust-ad&quot; enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then &quot;edns0&quot; and &quot;trust-ad&quot; are automatically added." />
+                  nmcli-description="DNS options for /etc/resolv.conf as described in resolv.conf(5) manual. The currently supported options are &quot;attempts&quot;, &quot;debug&quot;, &quot;edns0&quot;, &quot;ndots&quot;, &quot;no-aaaa&quot;, &quot;no-check-names&quot;, &quot;no-reload&quot;, &quot;no-tld-query&quot;, &quot;rotate&quot;, &quot;single-request&quot;, &quot;single-request-reopen&quot;, &quot;timeout&quot;, &quot;trust-ad&quot;, &quot;use-vc&quot; and &quot;inet6&quot;, &quot;ip6-bytestring&quot;, &quot;ip6-dotint&quot;, &quot;no-ip6-dotint&quot;. See the resolv.conf(5) manual. Note that there is a distinction between an unset (default) list and an empty list. In nmcli, to unset the list set the value to &quot;&quot;. To set an empty list, set it to &quot; &quot;. Currently, an unset list has the same meaning as an empty list. That might change in the future. The &quot;trust-ad&quot; setting is only honored if the profile contributes name servers to resolv.conf, and if all contributing profiles have &quot;trust-ad&quot; enabled. When using a caching DNS plugin (dnsmasq or systemd-resolved in NetworkManager.conf) then &quot;edns0&quot; and &quot;trust-ad&quot; are automatically added. The valid &quot;ipv4.dns-options&quot; and &quot;ipv6.dns-options&quot; get merged together." />
         <property name="dns-priority"
-                  description="DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the &quot;rotate&quot; option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the &apos;~.&apos; special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured." />
+                  nmcli-description="DNS servers priority. The relative priority for DNS servers specified by this setting.  A lower numerical value is better (higher priority). Negative values have the special effect of excluding other configurations with a greater numerical priority value; so in presence of at least one negative priority, only DNS servers from connections with the lowest priority value will be used. To avoid all DNS leaks, set the priority of the profile that should be used to the most negative value of all active connections profiles. Zero selects a globally configured default value. If the latter is missing or zero too, it defaults to 50 for VPNs (including WireGuard) and 100 for other connections. Note that the priority is to order DNS settings for multiple active connections.  It does not disambiguate multiple DNS servers within the same connection profile. When multiple devices have configurations with the same priority, VPNs will be considered first, then devices with the best (lowest metric) default route and then all other devices. When using dns=default, servers with higher priority will be on top of resolv.conf. To prioritize a given server over another one within the same connection, just specify them in the desired order. Note that commonly the resolver tries name servers in /etc/resolv.conf in the order listed, proceeding with the next server in the list on failure. See for example the &quot;rotate&quot; option of the dns-options setting. If there are any negative DNS priorities, then only name servers from the devices with that lowest priority will be considered. When using a DNS resolver that supports Conditional Forwarding or Split DNS (with dns=dnsmasq or dns=systemd-resolved settings), each connection is used to query domains in its search list. The search domains determine which name servers to ask, and the DNS priority is used to prioritize name servers based on the domain.  Queries for domains not present in any search list are routed through connections having the &apos;~.&apos; special wildcard domain, which is added automatically to connections with the default route (or can be added manually).  When multiple connections specify the same domain, the one with the best priority (lowest numerical value) wins.  If a sub domain is configured on another interface it will be accepted regardless the priority, unless parent domain on the other interface has a negative priority, which causes the sub domain to be shadowed. With Split DNS one can avoid undesired DNS leaks by properly configuring DNS priorities and the search domains, so that only name servers of the desired interface are configured." />
         <property name="addresses"
                   alias="ip6"
-                  description="A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example &quot;2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64&quot;. The addresses are listed in decreasing priority, meaning the first address will be the primary address. This can make a difference with IPv6 source address selection (RFC 6724, section 5)." />
+                  nmcli-description="A list of IPv6 addresses and their prefix length. Multiple addresses can be separated by comma. For example &quot;2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64&quot;. The addresses are listed in decreasing priority, meaning the first address will be the primary address. This can make a difference with IPv6 source address selection (RFC 6724, section 5)." />
         <property name="gateway"
                   alias="gw6"
-                  description="The gateway associated with this configuration. This is only meaningful if &quot;addresses&quot; is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if &quot;never-default&quot; is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See &quot;ip4-auto-default-route&quot;." />
+                  nmcli-description="The gateway associated with this configuration. This is only meaningful if &quot;addresses&quot; is also set. Setting the gateway causes NetworkManager to configure a standard default route with the gateway as next hop. This is ignored if &quot;never-default&quot; is set. An alternative is to configure the default route explicitly with a manual route and /0 as prefix length. Note that the gateway usually conflicts with routing that NetworkManager configures for WireGuard interfaces, so usually it should not be set in that case. See &quot;ip4-auto-default-route&quot;." />
         <property name="routes"
-                  description="Array of IP routes." />
+                  nmcli-description="Array of IP routes." />
         <property name="route-metric"
-                  description="The default metric for routes that don&apos;t explicitly specify a metric. The default value -1 means that the metric is chosen automatically based on the device type. The metric applies to dynamic routes, manual (static) routes that don&apos;t have an explicit metric setting, address prefix routes, and the default route. Note that for IPv6, the kernel accepts zero (0) but coerces it to 1024 (user default). Hence, setting this property to zero effectively mean setting it to 1024. For IPv4, zero is a regular value for the metric." />
+                  nmcli-description="The default metric for routes that don&apos;t explicitly specify a metric. The default value -1 means that the metric is chosen automatically based on the device type. The metric applies to dynamic routes, manual (static) routes that don&apos;t have an explicit metric setting, address prefix routes, and the default route. Note that for IPv6, the kernel accepts zero (0) but coerces it to 1024 (user default). Hence, setting this property to zero effectively mean setting it to 1024. For IPv4, zero is a regular value for the metric." />
         <property name="route-table"
-                  description="Enable policy routing (source routing) and set the routing table used when adding routes. This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes and static routes. But note that static routes can individually overwrite the setting by explicitly specifying a non-zero routing table. If the table setting is left at zero, it is eligible to be overwritten via global configuration. If the property is zero even after applying the global configuration value, policy routing is disabled for the address family of this connection. Policy routing disabled means that NetworkManager will add all routes to the main table (except static routes that explicitly configure a different table). Additionally, NetworkManager will not delete any extraneous routes from tables except the main table. This is to preserve backward compatibility for users who manage routing tables outside of NetworkManager." />
+                  nmcli-description="Enable policy routing (source routing) and set the routing table used when adding routes. This affects all routes, including device-routes, IPv4LL, DHCP, SLAAC, default-routes and static routes. But note that static routes can individually overwrite the setting by explicitly specifying a non-zero routing table. If the table setting is left at zero, it is eligible to be overwritten via global configuration. If the property is zero even after applying the global configuration value, policy routing is disabled for the address family of this connection. Policy routing disabled means that NetworkManager will add all routes to the main table (except static routes that explicitly configure a different table). Additionally, NetworkManager will not delete any extraneous routes from tables except the main table. This is to preserve backward compatibility for users who manage routing tables outside of NetworkManager." />
         <property name="routing-rules" />
         <property name="replace-local-rule"
-                  description="Connections will default to keep the autogenerated priority 0 local rule unless this setting is set to TRUE." />
+                  nmcli-description="Connections will default to keep the autogenerated priority 0 local rule unless this setting is set to TRUE." />
         <property name="ignore-auto-routes"
-                  description="When &quot;method&quot; is set to &quot;auto&quot; and this property to TRUE, automatically configured routes are ignored and only routes specified in the &quot;routes&quot; property, if any, are used." />
+                  nmcli-description="When &quot;method&quot; is set to &quot;auto&quot; and this property to TRUE, automatically configured routes are ignored and only routes specified in the &quot;routes&quot; property, if any, are used." />
         <property name="ignore-auto-dns"
-                  description="When &quot;method&quot; is set to &quot;auto&quot; and this property to TRUE, automatically configured name servers and search domains are ignored and only name servers and search domains specified in the &quot;dns&quot; and &quot;dns-search&quot; properties, if any, are used." />
+                  nmcli-description="When &quot;method&quot; is set to &quot;auto&quot; and this property to TRUE, automatically configured name servers and search domains are ignored and only name servers and search domains specified in the &quot;dns&quot; and &quot;dns-search&quot; properties, if any, are used." />
         <property name="never-default"
-                  description="If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager." />
+                  nmcli-description="If TRUE, this connection will never be the default connection for this IP type, meaning it will never be assigned the default route by NetworkManager." />
         <property name="may-fail"
-                  description="If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out.  Note that at least one IP configuration must succeed or overall network configuration will still fail.  For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully." />
+                  nmcli-description="If TRUE, allow overall network configuration to proceed even if the configuration specified by this property times out.  Note that at least one IP configuration must succeed or overall network configuration will still fail.  For example, in IPv6-only networks, setting this property to TRUE on the NMSettingIP4Config allows the overall network configuration to succeed if IPv4 configuration fails but IPv6 configuration completes successfully." />
         <property name="required-timeout"
-                  description="The minimum time interval in milliseconds for which dynamic IP configuration should be tried before the connection succeeds. This property is useful for example if both IPv4 and IPv6 are enabled and are allowed to fail. Normally the connection succeeds as soon as one of the two address families completes; by setting a required timeout for e.g. IPv4, one can ensure that even if IP6 succeeds earlier than IPv4, NetworkManager waits some time for IPv4 before the connection becomes active. Note that if &quot;may-fail&quot; is FALSE for the same address family, this property has no effect as NetworkManager needs to wait for the full DHCP timeout. A zero value means that no required timeout is present, -1 means the default value (either configuration ipvx.required-timeout override or zero)." />
+                  nmcli-description="The minimum time interval in milliseconds for which dynamic IP configuration should be tried before the connection succeeds. This property is useful for example if both IPv4 and IPv6 are enabled and are allowed to fail. Normally the connection succeeds as soon as one of the two address families completes; by setting a required timeout for e.g. IPv4, one can ensure that even if IP6 succeeds earlier than IPv4, NetworkManager waits some time for IPv4 before the connection becomes active. Note that if &quot;may-fail&quot; is FALSE for the same address family, this property has no effect as NetworkManager needs to wait for the full DHCP timeout. A zero value means that no required timeout is present, -1 means the default value (either configuration ipvx.required-timeout override or zero)." />
         <property name="ip6-privacy"
-                  description="Configure IPv6 Privacy Extensions for SLAAC, described in RFC4941.  If enabled, it makes the kernel generate a temporary IPv6 address in addition to the public one generated from MAC address via modified EUI-64.  This enhances privacy, but could cause problems in some applications, on the other hand.  The permitted values are: -1: unknown, 0: disabled, 1: enabled (prefer public address), 2: enabled (prefer temporary addresses). Having a per-connection setting set to &quot;-1&quot; (unknown) means fallback to global configuration &quot;ipv6.ip6-privacy&quot;. If also global configuration is unspecified or set to &quot;-1&quot;, fallback to read &quot;/proc/sys/net/ipv6/conf/default/use_tempaddr&quot;. Note that this setting is distinct from the Stable Privacy addresses that can be enabled with the &quot;addr-gen-mode&quot; property&apos;s &quot;stable-privacy&quot; setting as another way of avoiding host tracking with IPv6 addresses." />
+                  nmcli-description="Configure IPv6 Privacy Extensions for SLAAC, described in RFC4941.  If enabled, it makes the kernel generate a temporary IPv6 address in addition to the public one generated from MAC address via modified EUI-64.  This enhances privacy, but could cause problems in some applications, on the other hand.  The permitted values are: -1: unknown, 0: disabled, 1: enabled (prefer public address), 2: enabled (prefer temporary addresses). Having a per-connection setting set to &quot;-1&quot; (unknown) means fallback to global configuration &quot;ipv6.ip6-privacy&quot;. If also global configuration is unspecified or set to &quot;-1&quot;, fallback to read &quot;/proc/sys/net/ipv6/conf/default/use_tempaddr&quot;. Note that this setting is distinct from the Stable Privacy addresses that can be enabled with the &quot;addr-gen-mode&quot; property&apos;s &quot;stable-privacy&quot; setting as another way of avoiding host tracking with IPv6 addresses." />
         <property name="addr-gen-mode"
-                  description="Configure method for creating the address for use with RFC4862 IPv6 Stateless Address Autoconfiguration. The permitted values are: NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64 (0), NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY (1). NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT_OR_EUI64 (2) or NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_DEFAULT (3). If the property is set to EUI64, the addresses will be generated using the interface tokens derived from hardware address. This makes the host part of the address to stay constant, making it possible to track host&apos;s presence when it changes networks. The address changes when the interface hardware is replaced. The value of stable-privacy enables use of cryptographically secure hash of a secret host-specific key along with the connection&apos;s stable-id and the network address as specified by RFC7217. This makes it impossible to use the address track host&apos;s presence, and makes the address stable when the network interface hardware is replaced. The special values &quot;default&quot; and &quot;default-or-eui64&quot; will fallback to the global connection default in as documented in NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is &quot;stable-privacy&quot; or &quot;eui64&quot;, respectively. For libnm, the property defaults to &quot;default&quot; since 1.40. Previously it defaulted to &quot;stable-privacy&quot;. On D-Bus, the absence of an addr-gen-mode setting equals &quot;default&quot;. For keyfile plugin, the absence of the setting on disk means &quot;default-or-eui64&quot; so that the property doesn&apos;t change on upgrade from older versions. Note that this setting is distinct from the Privacy Extensions as configured by &quot;ip6-privacy&quot; property and it does not affect the temporary addresses configured with this option." />
+                  nmcli-description="Configure method for creating the IPv6 interface identifer of addresses with RFC4862 IPv6 Stateless Address Autoconfiguration and Link Local addresses. The permitted values are: &quot;eui64&quot; (0), &quot;stable-privacy&quot; (1), &quot;default&quot; (3) or &quot;default-or-eui64&quot; (2). If the property is set to &quot;eui64&quot;, the addresses will be generated using the interface token derived from hardware address. This makes the host part of the address to stay constant, making it possible to track the host&apos;s presence when it changes networks. The address changes when the interface hardware is replaced. If a duplicate address is detected, there is also no fallback to generate another address. When configured, the &quot;ipv6.token&quot; is used instead of the MAC address to generate addresses for stateless autoconfiguration. If the property is set to &quot;stable-privacy&quot;, the interface identifier is generated as specified by RFC7217. This works by hashing a host specific key (see NetworkManager(8) manual), the interface name, the connection&apos;s &quot;connection.stable-id&quot; property and the address prefix.  This improves privacy by making it harder to use the address to track the host&apos;s presence and the address is stable when the network interface hardware is replaced. The special values &quot;default&quot; and &quot;default-or-eui64&quot; will fallback to the global connection default as documented in the NetworkManager.conf(5) manual. If the global default is not specified, the fallback value is &quot;stable-privacy&quot; or &quot;eui64&quot;, respectively. If not specified, when creating a new profile the default is &quot;default&quot;. Note that this setting is distinct from the Privacy Extensions as configured by &quot;ip6-privacy&quot; property and it does not affect the temporary addresses configured with this option." />
         <property name="ra-timeout"
-                  description="A timeout for waiting Router Advertisements in seconds. If zero (the default), a globally configured default is used. If still unspecified, the timeout depends on the sysctl settings of the device. Set to 2147483647 (MAXINT32) for infinity." />
+                  nmcli-description="A timeout for waiting Router Advertisements in seconds. If zero (the default), a globally configured default is used. If still unspecified, the timeout depends on the sysctl settings of the device. Set to 2147483647 (MAXINT32) for infinity." />
         <property name="mtu"
-                  description="Maximum transmission unit size, in bytes. If zero (the default), the MTU is set automatically from router advertisements or is left equal to the link-layer MTU. If greater than the link-layer MTU, or greater than zero but less than the minimum IPv6 MTU of 1280, this value has no effect." />
+                  nmcli-description="Maximum transmission unit size, in bytes. If zero (the default), the MTU is set automatically from router advertisements or is left equal to the link-layer MTU. If greater than the link-layer MTU, or greater than zero but less than the minimum IPv6 MTU of 1280, this value has no effect." />
+        <property name="dhcp-pd-hint"
+                  nmcli-description="A IPv6 address followed by a slash and a prefix length. If set, the value is sent to the DHCPv6 server as hint indicating the prefix delegation (IA_PD) we want to receive. To only hint a prefix length without prefix, set the address part to the zero address (for example &quot;::/60&quot;)." />
         <property name="dhcp-duid"
-                  description="A string containing the DHCPv6 Unique Identifier (DUID) used by the dhcp client to identify itself to DHCPv6 servers (RFC 3315). The DUID is carried in the Client Identifier option. If the property is a hex string (&apos;aa:bb:cc&apos;) it is interpreted as a binary DUID and filled as an opaque value in the Client Identifier option. The special value &quot;lease&quot; will retrieve the DUID previously used from the lease file belonging to the connection. If no DUID is found and &quot;dhclient&quot; is the configured dhcp client, the DUID is searched in the system-wide dhclient lease file. If still no DUID is found, or another dhcp client is used, a global and permanent DUID-UUID (RFC 6355) will be generated based on the machine-id. The special values &quot;llt&quot; and &quot;ll&quot; will generate a DUID of type LLT or LL (see RFC 3315) based on the current MAC address of the device. In order to try providing a stable DUID-LLT, the time field will contain a constant timestamp that is used globally (for all profiles) and persisted to disk. The special values &quot;stable-llt&quot;, &quot;stable-ll&quot; and &quot;stable-uuid&quot; will generate a DUID of the corresponding type, derived from the connection&apos;s stable-id and a per-host unique key. You may want to include the &quot;${DEVICE}&quot; or &quot;${MAC}&quot; specifier in the stable-id, in case this profile gets activated on multiple devices. So, the link-layer address of &quot;stable-ll&quot; and &quot;stable-llt&quot; will be a generated address derived from the stable id. The DUID-LLT time value in the &quot;stable-llt&quot; option will be picked among a static timespan of three years (the upper bound of the interval is the same constant timestamp used in &quot;llt&quot;). When the property is unset, the global value provided for &quot;ipv6.dhcp-duid&quot; is used. If no global value is provided, the default &quot;lease&quot; value is assumed." />
+                  nmcli-description="A string containing the DHCPv6 Unique Identifier (DUID) used by the dhcp client to identify itself to DHCPv6 servers (RFC 3315). The DUID is carried in the Client Identifier option. If the property is a hex string (&apos;aa:bb:cc&apos;) it is interpreted as a binary DUID and filled as an opaque value in the Client Identifier option. The special value &quot;lease&quot; will retrieve the DUID previously used from the lease file belonging to the connection. If no DUID is found and &quot;dhclient&quot; is the configured dhcp client, the DUID is searched in the system-wide dhclient lease file. If still no DUID is found, or another dhcp client is used, a global and permanent DUID-UUID (RFC 6355) will be generated based on the machine-id. The special values &quot;llt&quot; and &quot;ll&quot; will generate a DUID of type LLT or LL (see RFC 3315) based on the current MAC address of the device. In order to try providing a stable DUID-LLT, the time field will contain a constant timestamp that is used globally (for all profiles) and persisted to disk. The special values &quot;stable-llt&quot;, &quot;stable-ll&quot; and &quot;stable-uuid&quot; will generate a DUID of the corresponding type, derived from the connection&apos;s stable-id and a per-host unique key. You may want to include the &quot;${DEVICE}&quot; or &quot;${MAC}&quot; specifier in the stable-id, in case this profile gets activated on multiple devices. So, the link-layer address of &quot;stable-ll&quot; and &quot;stable-llt&quot; will be a generated address derived from the stable id. The DUID-LLT time value in the &quot;stable-llt&quot; option will be picked among a static timespan of three years (the upper bound of the interval is the same constant timestamp used in &quot;llt&quot;). When the property is unset, the global value provided for &quot;ipv6.dhcp-duid&quot; is used. If no global value is provided, the default &quot;lease&quot; value is assumed." />
         <property name="dhcp-iaid"
-                  description="A string containing the &quot;Identity Association Identifier&quot; (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or or as colon separated hexadecimal numbers). Alternatively it can be set to the special values &quot;mac&quot;, &quot;perm-mac&quot;, &quot;ifname&quot; or &quot;stable&quot;. When set to &quot;mac&quot; (or &quot;perm-mac&quot;), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to &quot;ifname&quot;, the IAID is computed by hashing the interface name. The special value &quot;stable&quot; can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be &quot;ifname&quot;. For DHCPv4, the IAID is only used with &quot;ipv4.dhcp-client-id&quot; values &quot;duid&quot; and &quot;ipv6-duid&quot; to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the &quot;internal&quot; DHCPv6 plugin. The &quot;dhclient&quot; DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device." />
+                  nmcli-description="A string containing the &quot;Identity Association Identifier&quot; (IAID) used by the DHCP client. The string can be a 32-bit number (either decimal, hexadecimal or as colon separated hexadecimal numbers). Alternatively it can be set to the special values &quot;mac&quot;, &quot;perm-mac&quot;, &quot;ifname&quot; or &quot;stable&quot;. When set to &quot;mac&quot; (or &quot;perm-mac&quot;), the last 4 bytes of the current (or permanent) MAC address are used as IAID. When set to &quot;ifname&quot;, the IAID is computed by hashing the interface name. The special value &quot;stable&quot; can be used to generate an IAID based on the stable-id (see connection.stable-id), a per-host key and the interface name. When the property is unset, the value from global configuration is used; if no global default is set then the IAID is assumed to be &quot;ifname&quot;. For DHCPv4, the IAID is only used with &quot;ipv4.dhcp-client-id&quot; values &quot;duid&quot; and &quot;ipv6-duid&quot; to generate the client-id. For DHCPv6, note that at the moment this property is only supported by the &quot;internal&quot; DHCPv6 plugin. The &quot;dhclient&quot; DHCPv6 plugin always derives the IAID from the MAC address. The actually used DHCPv6 IAID for a currently activated interface is exposed in the lease information of the device." />
         <property name="dhcp-timeout"
-                  description="A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity." />
+                  nmcli-description="A timeout for a DHCP transaction in seconds. If zero (the default), a globally configured default is used. If still unspecified, a device specific timeout is used (usually 45 seconds). Set to 2147483647 (MAXINT32) for infinity." />
         <property name="dhcp-send-hostname"
-                  description="If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the &quot;dhcp-hostname&quot; property is NULL and this property is TRUE, the current persistent hostname of the computer is sent." />
+                  nmcli-description="If TRUE, a hostname is sent to the DHCP server when acquiring a lease. Some DHCP servers use this hostname to update DNS databases, essentially providing a static hostname for the computer.  If the &quot;dhcp-hostname&quot; property is NULL and this property is TRUE, the current persistent hostname of the computer is sent." />
         <property name="dhcp-hostname"
-                  description="If the &quot;dhcp-send-hostname&quot; property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and &quot;dhcp-fqdn&quot; are mutually exclusive and cannot be set at the same time." />
+                  nmcli-description="If the &quot;dhcp-send-hostname&quot; property is TRUE, then the specified name will be sent to the DHCP server when acquiring a lease. This property and &quot;dhcp-fqdn&quot; are mutually exclusive and cannot be set at the same time." />
         <property name="dhcp-hostname-flags"
-                  description="Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) and NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE (0x4).  When no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is not set, the standard FQDN flags are set in the request: NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) for IPv4 and NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1) for IPv6. When this property is set to the default value NM_DHCP_HOSTNAME_FLAG_NONE (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also NM_DHCP_HOSTNAME_FLAG_NONE (0x0), then the standard FQDN flags described above are sent in the DHCP requests." />
+                  nmcli-description="Flags for the DHCP hostname and FQDN. Currently, this property only includes flags to control the FQDN flags set in the DHCP FQDN option. Supported FQDN flags are NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) and NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE (0x4).  When no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is set, the DHCP FQDN option will contain no flag. Otherwise, if no FQDN flag is set and NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS (0x8) is not set, the standard FQDN flags are set in the request: NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1), NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED (0x2) for IPv4 and NM_DHCP_HOSTNAME_FLAG_FQDN_SERV_UPDATE (0x1) for IPv6. When this property is set to the default value NM_DHCP_HOSTNAME_FLAG_NONE (0x0), a global default is looked up in NetworkManager configuration. If that value is unset or also NM_DHCP_HOSTNAME_FLAG_NONE (0x0), then the standard FQDN flags described above are sent in the DHCP requests." />
         <property name="auto-route-ext-gw"
-                  description="VPN connections will default to add the route automatically unless this setting is set to FALSE. For other connection types, adding such an automatic route is currently not supported and setting this to TRUE has no effect." />
+                  nmcli-description="VPN connections will default to add the route automatically unless this setting is set to FALSE. For other connection types, adding such an automatic route is currently not supported and setting this to TRUE has no effect." />
         <property name="token"
-                  description="Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode." />
+                  nmcli-description="Configure the token for draft-chown-6man-tokenised-ipv6-identifiers-02 IPv6 tokenized interface identifiers. Useful with eui64 addr-gen-mode. When set, the token is used as IPv6 interface identifier instead of the hardware address. This only applies to addresses from stateless autoconfiguration, not to IPv6 link local addresses." />
+    </setting>
+    <setting name="link" >
+        <property name="gso-max-segments"
+                  nmcli-description="The maximum segments of a Generic Segment Offload packet the device should accept. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved." />
+        <property name="gso-max-size"
+                  nmcli-description="The maximum size of a Generic Segment Offload packet the device should accept. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved." />
+        <property name="gro-max-size"
+                  nmcli-description="The maximum size of a packet built by the Generic Receive Offload stack for this device. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved." />
+        <property name="tx-queue-length"
+                  nmcli-description="The size of the transmit queue for the device, in number of packets. The value must be between 0 and 4294967295. When set to -1, the existing value is preserved." />
     </setting>
     <setting name="loopback" >
         <property name="mtu"
                   alias="mtu"
-                  description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames." />
+                  nmcli-description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple Ethernet frames." />
     </setting>
     <setting name="macsec" >
         <property name="parent"
                   alias="dev"
-                  description="If given, specifies the parent interface name or parent connection UUID from which this MACSEC interface should be created.  If this property is not specified, the connection must contain an &quot;802-3-ethernet&quot; setting with a &quot;mac-address&quot; property." />
+                  nmcli-description="If given, specifies the parent interface name or parent connection UUID from which this MACSEC interface should be created.  If this property is not specified, the connection must contain an &quot;802-3-ethernet&quot; setting with a &quot;mac-address&quot; property." />
         <property name="mode"
                   alias="mode"
-                  description="Specifies how the CAK (Connectivity Association Key) for MKA (MACsec Key Agreement) is obtained." />
+                  nmcli-description="Specifies how the CAK (Connectivity Association Key) for MKA (MACsec Key Agreement) is obtained." />
         <property name="encrypt"
                   alias="encrypt"
-                  description="Whether the transmitted traffic must be encrypted." />
+                  nmcli-description="Whether the transmitted traffic must be encrypted." />
         <property name="mka-cak"
                   alias="cak"
-                  description="The pre-shared CAK (Connectivity Association Key) for MACsec Key Agreement. Must be a string of 32 hexadecimal characters." />
+                  nmcli-description="The pre-shared CAK (Connectivity Association Key) for MACsec Key Agreement. Must be a string of 32 hexadecimal characters." />
         <property name="mka-cak-flags"
-                  description="Flags indicating how to handle the &quot;mka-cak&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;mka-cak&quot; property." />
         <property name="mka-ckn"
                   alias="ckn"
-                  description="The pre-shared CKN (Connectivity-association Key Name) for MACsec Key Agreement. Must be a string of hexadecimal characters with a even length between 2 and 64." />
+                  nmcli-description="The pre-shared CKN (Connectivity-association Key Name) for MACsec Key Agreement. Must be a string of hexadecimal characters with a even length between 2 and 64." />
         <property name="port"
                   alias="port"
-                  description="The port component of the SCI (Secure Channel Identifier), between 1 and 65534." />
+                  nmcli-description="The port component of the SCI (Secure Channel Identifier), between 1 and 65534." />
         <property name="validation"
-                  description="Specifies the validation mode for incoming frames." />
+                  nmcli-description="Specifies the validation mode for incoming frames." />
         <property name="send-sci"
-                  description="Specifies whether the SCI (Secure Channel Identifier) is included in every packet." />
+                  nmcli-description="Specifies whether the SCI (Secure Channel Identifier) is included in every packet." />
     </setting>
     <setting name="macvlan" >
         <property name="parent"
                   alias="dev"
-                  description="If given, specifies the parent interface name or parent connection UUID from which this MAC-VLAN interface should be created.  If this property is not specified, the connection must contain an &quot;802-3-ethernet&quot; setting with a &quot;mac-address&quot; property." />
+                  nmcli-description="If given, specifies the parent interface name or parent connection UUID from which this MAC-VLAN interface should be created.  If this property is not specified, the connection must contain an &quot;802-3-ethernet&quot; setting with a &quot;mac-address&quot; property." />
         <property name="mode"
                   alias="mode"
-                  description="The macvlan mode, which specifies the communication mechanism between multiple macvlans on the same lower device." />
+                  nmcli-description="The macvlan mode, which specifies the communication mechanism between multiple macvlans on the same lower device." />
         <property name="promiscuous"
-                  description="Whether the interface should be put in promiscuous mode." />
+                  nmcli-description="Whether the interface should be put in promiscuous mode." />
         <property name="tap"
                   alias="tap"
-                  description="Whether the interface should be a MACVTAP." />
+                  nmcli-description="Whether the interface should be a MACVTAP." />
     </setting>
     <setting name="match" >
         <property name="interface-name"
-                  description="A list of interface names to match. Each element is a shell wildcard pattern. An element can be prefixed with a pipe symbol (|) or an ampersand (&amp;). The former means that the element is optional and the latter means that it is mandatory. If there are any optional elements, than the match evaluates to true if at least one of the optional element matches (logical OR). If there are any mandatory elements, then they all must match (logical AND). By default, an element is optional. This means that an element &quot;foo&quot; behaves the same as &quot;|foo&quot;. An element can also be inverted with exclamation mark (!) between the pipe symbol (or the ampersand) and before the pattern. Note that &quot;!foo&quot; is a shortcut for the mandatory match &quot;&amp;!foo&quot;. Finally, a backslash can be used at the beginning of the element (after the optional special characters) to escape the start of the pattern. For example, &quot;&amp;\!a&quot; is an mandatory match for literally &quot;!a&quot;." />
+                  nmcli-description="A list of interface names to match. Each element is a shell wildcard pattern. An element can be prefixed with a pipe symbol (|) or an ampersand (&amp;). The former means that the element is optional and the latter means that it is mandatory. If there are any optional elements, than the match evaluates to true if at least one of the optional element matches (logical OR). If there are any mandatory elements, then they all must match (logical AND). By default, an element is optional. This means that an element &quot;foo&quot; behaves the same as &quot;|foo&quot;. An element can also be inverted with exclamation mark (!) between the pipe symbol (or the ampersand) and before the pattern. Note that &quot;!foo&quot; is a shortcut for the mandatory match &quot;&amp;!foo&quot;. Finally, a backslash can be used at the beginning of the element (after the optional special characters) to escape the start of the pattern. For example, &quot;&amp;\!a&quot; is an mandatory match for literally &quot;!a&quot;." />
         <property name="kernel-command-line"
-                  description="A list of kernel command line arguments to match. This may be used to check whether a specific kernel command line option is set (or unset, if prefixed with the exclamation mark). The argument must either be a single word, or an assignment (i.e. two words, joined by &quot;=&quot;). In the former case the kernel command line is searched for the word appearing as is, or as left hand side of an assignment. In the latter case, the exact assignment is looked for with right and left hand side matching. Wildcard patterns are not supported. See NMSettingMatch:interface-name for how special characters &apos;|&apos;, &apos;&amp;&apos;, &apos;!&apos; and &apos;\&apos; are used for optional and mandatory matches and inverting the match." />
+                  nmcli-description="A list of kernel command line arguments to match. This may be used to check whether a specific kernel command line option is set (or unset, if prefixed with the exclamation mark). The argument must either be a single word, or an assignment (i.e. two words, joined by &quot;=&quot;). In the former case the kernel command line is searched for the word appearing as is, or as left hand side of an assignment. In the latter case, the exact assignment is looked for with right and left hand side matching. Wildcard patterns are not supported. See NMSettingMatch:interface-name for how special characters &apos;|&apos;, &apos;&amp;&apos;, &apos;!&apos; and &apos;\&apos; are used for optional and mandatory matches and inverting the match." />
         <property name="driver"
-                  description="A list of driver names to match. Each element is a shell wildcard pattern. See NMSettingMatch:interface-name for how special characters &apos;|&apos;, &apos;&amp;&apos;, &apos;!&apos; and &apos;\&apos; are used for optional and mandatory matches and inverting the pattern." />
+                  nmcli-description="A list of driver names to match. Each element is a shell wildcard pattern. See NMSettingMatch:interface-name for how special characters &apos;|&apos;, &apos;&amp;&apos;, &apos;!&apos; and &apos;\&apos; are used for optional and mandatory matches and inverting the pattern." />
         <property name="path"
-                  description="A list of paths to match against the ID_PATH udev property of devices. ID_PATH represents the topological persistent path of a device. It typically contains a subsystem string (pci, usb, platform, etc.) and a subsystem-specific identifier. For PCI devices the path has the form &quot;pci-$domain:$bus:$device.$function&quot;, where each variable is an hexadecimal value; for example &quot;pci-0000:0a:00.0&quot;. The path of a device can be obtained with &quot;udevadm info /sys/class/net/$dev | grep ID_PATH=&quot; or by looking at the &quot;path&quot; property exported by NetworkManager (&quot;nmcli -f general.path device show $dev&quot;). Each element of the list is a shell wildcard pattern. See NMSettingMatch:interface-name for how special characters &apos;|&apos;, &apos;&amp;&apos;, &apos;!&apos; and &apos;\&apos; are used for optional and mandatory matches and inverting the pattern." />
+                  nmcli-description="A list of paths to match against the ID_PATH udev property of devices. ID_PATH represents the topological persistent path of a device. It typically contains a subsystem string (pci, usb, platform, etc.) and a subsystem-specific identifier. For PCI devices the path has the form &quot;pci-$domain:$bus:$device.$function&quot;, where each variable is an hexadecimal value; for example &quot;pci-0000:0a:00.0&quot;. The path of a device can be obtained with &quot;udevadm info /sys/class/net/$dev | grep ID_PATH=&quot; or by looking at the &quot;path&quot; property exported by NetworkManager (&quot;nmcli -f general.path device show $dev&quot;). Each element of the list is a shell wildcard pattern. See NMSettingMatch:interface-name for how special characters &apos;|&apos;, &apos;&amp;&apos;, &apos;!&apos; and &apos;\&apos; are used for optional and mandatory matches and inverting the pattern." />
     </setting>
     <setting name="ovs-bridge" >
         <property name="fail-mode"
-                  description="The bridge failure mode. One of &quot;secure&quot;, &quot;standalone&quot; or empty." />
+                  nmcli-description="The bridge failure mode. One of &quot;secure&quot;, &quot;standalone&quot; or empty." />
         <property name="mcast-snooping-enable"
-                  description="Enable or disable multicast snooping." />
+                  nmcli-description="Enable or disable multicast snooping." />
         <property name="rstp-enable"
-                  description="Enable or disable RSTP." />
+                  nmcli-description="Enable or disable RSTP." />
         <property name="stp-enable"
-                  description="Enable or disable STP." />
+                  nmcli-description="Enable or disable STP." />
         <property name="datapath-type"
-                  description="The data path type. One of &quot;system&quot;, &quot;netdev&quot; or empty." />
+                  nmcli-description="The data path type. One of &quot;system&quot;, &quot;netdev&quot; or empty." />
     </setting>
     <setting name="ovs-dpdk" >
         <property name="devargs"
-                  description="Open vSwitch DPDK device arguments." />
+                  nmcli-description="Open vSwitch DPDK device arguments." />
         <property name="n-rxq"
-                  description="Open vSwitch DPDK number of rx queues. Defaults to zero which means to leave the parameter in OVS unspecified and effectively configures one queue." />
+                  nmcli-description="Open vSwitch DPDK number of rx queues. Defaults to zero which means to leave the parameter in OVS unspecified and effectively configures one queue." />
         <property name="n-rxq-desc"
-                  description="The rx queue size (number of rx descriptors) for DPDK ports. Must be zero or a power of 2 between 1 and 4096, and supported by the hardware. Defaults to zero which means to leave the parameter in OVS unspecified and effectively configures 2048 descriptors." />
+                  nmcli-description="The rx queue size (number of rx descriptors) for DPDK ports. Must be zero or a power of 2 between 1 and 4096, and supported by the hardware. Defaults to zero which means to leave the parameter in OVS unspecified and effectively configures 2048 descriptors." />
         <property name="n-txq-desc"
-                  description="The tx queue size (number of tx descriptors) for DPDK ports. Must be zero or a power of 2 between 1 and 4096, and supported by the hardware. Defaults to zero which means to leave the parameter in OVS unspecified and effectively configures 2048 descriptors." />
+                  nmcli-description="The tx queue size (number of tx descriptors) for DPDK ports. Must be zero or a power of 2 between 1 and 4096, and supported by the hardware. Defaults to zero which means to leave the parameter in OVS unspecified and effectively configures 2048 descriptors." />
     </setting>
     <setting name="ovs-external-ids" >
     </setting>
     <setting name="ovs-interface" >
         <property name="type"
-                  description="The interface type. Either &quot;internal&quot;, &quot;system&quot;, &quot;patch&quot;, &quot;dpdk&quot;, or empty." />
+                  nmcli-description="The interface type. Either &quot;internal&quot;, &quot;system&quot;, &quot;patch&quot;, &quot;dpdk&quot;, or empty." />
         <property name="ofport-request"
-                  description="Open vSwitch openflow port number. Defaults to zero which means that port number will not be specified and it will be chosen randomly by ovs. OpenFlow ports are the network interfaces for passing packets between OpenFlow processing and the rest of the network. OpenFlow switches connect logically to each other via their OpenFlow ports." />
+                  nmcli-description="Open vSwitch openflow port number. Defaults to zero which means that port number will not be specified and it will be chosen randomly by ovs. OpenFlow ports are the network interfaces for passing packets between OpenFlow processing and the rest of the network. OpenFlow switches connect logically to each other via their OpenFlow ports." />
     </setting>
     <setting name="ovs-other-config" >
     </setting>
     <setting name="ovs-patch" >
         <property name="peer"
-                  description="Specifies the name of the interface for the other side of the patch. The patch on the other side must also set this interface as peer." />
+                  nmcli-description="Specifies the name of the interface for the other side of the patch. The patch on the other side must also set this interface as peer." />
     </setting>
     <setting name="ovs-port" >
         <property name="vlan-mode"
-                  description="The VLAN mode. One of &quot;access&quot;, &quot;native-tagged&quot;, &quot;native-untagged&quot;, &quot;trunk&quot;, &quot;dot1q-tunnel&quot; or unset." />
+                  nmcli-description="The VLAN mode. One of &quot;access&quot;, &quot;native-tagged&quot;, &quot;native-untagged&quot;, &quot;trunk&quot;, &quot;dot1q-tunnel&quot; or unset." />
         <property name="trunks"
-                  description="A list of VLAN ranges that this port trunks. The property is valid only for ports with mode &quot;trunk&quot;, &quot;native-tagged&quot;, or &quot;native-untagged port&quot;. If it is empty, the port trunks all VLANs." />
+                  nmcli-description="A list of VLAN ranges that this port trunks. The property is valid only for ports with mode &quot;trunk&quot;, &quot;native-tagged&quot;, or &quot;native-untagged port&quot;. If it is empty, the port trunks all VLANs." />
         <property name="tag"
-                  description="The VLAN tag in the range 0-4095." />
+                  nmcli-description="The VLAN tag in the range 0-4095." />
         <property name="lacp"
-                  description="LACP mode. One of &quot;active&quot;, &quot;off&quot;, or &quot;passive&quot;." />
+                  nmcli-description="LACP mode. One of &quot;active&quot;, &quot;off&quot;, or &quot;passive&quot;." />
         <property name="bond-mode"
-                  description="Bonding mode. One of &quot;active-backup&quot;, &quot;balance-slb&quot;, or &quot;balance-tcp&quot;." />
+                  nmcli-description="Bonding mode. One of &quot;active-backup&quot;, &quot;balance-slb&quot;, or &quot;balance-tcp&quot;." />
         <property name="bond-updelay"
-                  description="The time port must be active before it starts forwarding traffic." />
+                  nmcli-description="The time port must be active before it starts forwarding traffic." />
         <property name="bond-downdelay"
-                  description="The time port must be inactive in order to be considered down." />
+                  nmcli-description="The time port must be inactive in order to be considered down." />
     </setting>
     <setting name="ppp" >
         <property name="noauth"
-                  description="If TRUE, do not require the other side (usually the PPP server) to authenticate itself to the client.  If FALSE, require authentication from the remote side.  In almost all cases, this should be TRUE." />
+                  nmcli-description="If TRUE, do not require the other side (usually the PPP server) to authenticate itself to the client.  If FALSE, require authentication from the remote side.  In almost all cases, this should be TRUE." />
         <property name="refuse-eap"
-                  description="If TRUE, the EAP authentication method will not be used." />
+                  nmcli-description="If TRUE, the EAP authentication method will not be used." />
         <property name="refuse-pap"
-                  description="If TRUE, the PAP authentication method will not be used." />
+                  nmcli-description="If TRUE, the PAP authentication method will not be used." />
         <property name="refuse-chap"
-                  description="If TRUE, the CHAP authentication method will not be used." />
+                  nmcli-description="If TRUE, the CHAP authentication method will not be used." />
         <property name="refuse-mschap"
-                  description="If TRUE, the MSCHAP authentication method will not be used." />
+                  nmcli-description="If TRUE, the MSCHAP authentication method will not be used." />
         <property name="refuse-mschapv2"
-                  description="If TRUE, the MSCHAPv2 authentication method will not be used." />
+                  nmcli-description="If TRUE, the MSCHAPv2 authentication method will not be used." />
         <property name="nobsdcomp"
-                  description="If TRUE, BSD compression will not be requested." />
+                  nmcli-description="If TRUE, BSD compression will not be requested." />
         <property name="nodeflate"
-                  description="If TRUE, &quot;deflate&quot; compression will not be requested." />
+                  nmcli-description="If TRUE, &quot;deflate&quot; compression will not be requested." />
         <property name="no-vj-comp"
-                  description="If TRUE, Van Jacobsen TCP header compression will not be requested." />
+                  nmcli-description="If TRUE, Van Jacobsen TCP header compression will not be requested." />
         <property name="require-mppe"
-                  description="If TRUE, MPPE (Microsoft Point-to-Point Encryption) will be required for the PPP session.  If either 64-bit or 128-bit MPPE is not available the session will fail.  Note that MPPE is not used on mobile broadband connections." />
+                  nmcli-description="If TRUE, MPPE (Microsoft Point-to-Point Encryption) will be required for the PPP session.  If either 64-bit or 128-bit MPPE is not available the session will fail.  Note that MPPE is not used on mobile broadband connections." />
         <property name="require-mppe-128"
-                  description="If TRUE, 128-bit MPPE (Microsoft Point-to-Point Encryption) will be required for the PPP session, and the &quot;require-mppe&quot; property must also be set to TRUE.  If 128-bit MPPE is not available the session will fail." />
+                  nmcli-description="If TRUE, 128-bit MPPE (Microsoft Point-to-Point Encryption) will be required for the PPP session, and the &quot;require-mppe&quot; property must also be set to TRUE.  If 128-bit MPPE is not available the session will fail." />
         <property name="mppe-stateful"
-                  description="If TRUE, stateful MPPE is used.  See pppd documentation for more information on stateful MPPE." />
+                  nmcli-description="If TRUE, stateful MPPE is used.  See pppd documentation for more information on stateful MPPE." />
         <property name="crtscts"
-                  description="If TRUE, specify that pppd should set the serial port to use hardware flow control with RTS and CTS signals.  This value should normally be set to FALSE." />
+                  nmcli-description="If TRUE, specify that pppd should set the serial port to use hardware flow control with RTS and CTS signals.  This value should normally be set to FALSE." />
         <property name="baud"
-                  description="If non-zero, instruct pppd to set the serial port to the specified baudrate.  This value should normally be left as 0 to automatically choose the speed." />
+                  nmcli-description="If non-zero, instruct pppd to set the serial port to the specified baudrate.  This value should normally be left as 0 to automatically choose the speed." />
         <property name="mru"
-                  description="If non-zero, instruct pppd to request that the peer send packets no larger than the specified size.  If non-zero, the MRU should be between 128 and 16384." />
+                  nmcli-description="If non-zero, instruct pppd to request that the peer send packets no larger than the specified size.  If non-zero, the MRU should be between 128 and 16384." />
         <property name="mtu"
-                  description="If non-zero, instruct pppd to send packets no larger than the specified size." />
+                  nmcli-description="If non-zero, instruct pppd to send packets no larger than the specified size." />
         <property name="lcp-echo-failure"
-                  description="If non-zero, instruct pppd to presume the connection to the peer has failed if the specified number of LCP echo-requests go unanswered by the peer.  The &quot;lcp-echo-interval&quot; property must also be set to a non-zero value if this property is used." />
+                  nmcli-description="If non-zero, instruct pppd to presume the connection to the peer has failed if the specified number of LCP echo-requests go unanswered by the peer.  The &quot;lcp-echo-interval&quot; property must also be set to a non-zero value if this property is used." />
         <property name="lcp-echo-interval"
-                  description="If non-zero, instruct pppd to send an LCP echo-request frame to the peer every n seconds (where n is the specified value).  Note that some PPP peers will respond to echo requests and some will not, and it is not possible to autodetect this." />
+                  nmcli-description="If non-zero, instruct pppd to send an LCP echo-request frame to the peer every n seconds (where n is the specified value).  Note that some PPP peers will respond to echo requests and some will not, and it is not possible to autodetect this." />
     </setting>
     <setting name="pppoe" >
         <property name="parent"
                   alias="parent"
-                  description="If given, specifies the parent interface name on which this PPPoE connection should be created.  If this property is not specified, the connection is activated on the interface specified in &quot;interface-name&quot; of NMSettingConnection." />
+                  nmcli-description="If given, specifies the parent interface name on which this PPPoE connection should be created.  If this property is not specified, the connection is activated on the interface specified in &quot;interface-name&quot; of NMSettingConnection." />
         <property name="service"
                   alias="service"
-                  description="If specified, instruct PPPoE to only initiate sessions with access concentrators that provide the specified service.  For most providers, this should be left blank.  It is only required if there are multiple access concentrators or a specific service is known to be required." />
+                  nmcli-description="If specified, instruct PPPoE to only initiate sessions with access concentrators that provide the specified service.  For most providers, this should be left blank.  It is only required if there are multiple access concentrators or a specific service is known to be required." />
         <property name="username"
                   alias="username"
-                  description="Username used to authenticate with the PPPoE service." />
+                  nmcli-description="Username used to authenticate with the PPPoE service." />
         <property name="password"
                   alias="password"
-                  description="Password used to authenticate with the PPPoE service." />
+                  nmcli-description="Password used to authenticate with the PPPoE service." />
         <property name="password-flags"
-                  description="Flags indicating how to handle the &quot;password&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;password&quot; property." />
     </setting>
     <setting name="proxy" >
         <property name="method"
                   alias="method"
-                  description="Method for proxy configuration, Default is NM_SETTING_PROXY_METHOD_NONE (0)" />
+                  nmcli-description="Method for proxy configuration, Default is NM_SETTING_PROXY_METHOD_NONE (0)" />
         <property name="browser-only"
                   alias="browser-only"
-                  description="Whether the proxy configuration is for browser only." />
+                  nmcli-description="Whether the proxy configuration is for browser only." />
         <property name="pac-url"
                   alias="pac-url"
-                  description="PAC URL for obtaining PAC file." />
+                  nmcli-description="PAC URL for obtaining PAC file." />
         <property name="pac-script"
                   alias="pac-script"
-                  description="The PAC script. In the profile this must be an UTF-8 encoded javascript code that defines a FindProxyForURL() function. When setting the property in nmcli, a filename is accepted too. In that case, nmcli will read the content of the file and set the script. The prefixes &quot;file://&quot; and &quot;js://&quot; are supported to explicitly differentiate between the two." />
+                  nmcli-description="The PAC script. In the profile this must be an UTF-8 encoded javascript code that defines a FindProxyForURL() function. When setting the property in nmcli, a filename is accepted too. In that case, nmcli will read the content of the file and set the script. The prefixes &quot;file://&quot; and &quot;js://&quot; are supported to explicitly differentiate between the two." />
     </setting>
     <setting name="serial" >
         <property name="baud"
-                  description="Speed to use for communication over the serial port.  Note that this value usually has no effect for mobile broadband modems as they generally ignore speed settings and use the highest available speed." />
+                  nmcli-description="Speed to use for communication over the serial port.  Note that this value usually has no effect for mobile broadband modems as they generally ignore speed settings and use the highest available speed." />
         <property name="bits"
-                  description="Byte-width of the serial communication. The 8 in &quot;8n1&quot; for example." />
+                  nmcli-description="Byte-width of the serial communication. The 8 in &quot;8n1&quot; for example." />
         <property name="parity"
-                  description="Parity setting of the serial port." />
+                  nmcli-description="Parity setting of the serial port." />
         <property name="stopbits"
-                  description="Number of stop bits for communication on the serial port.  Either 1 or 2. The 1 in &quot;8n1&quot; for example." />
+                  nmcli-description="Number of stop bits for communication on the serial port.  Either 1 or 2. The 1 in &quot;8n1&quot; for example." />
         <property name="send-delay"
-                  description="Time to delay between each byte sent to the modem, in microseconds." />
+                  nmcli-description="Time to delay between each byte sent to the modem, in microseconds." />
     </setting>
     <setting name="sriov" >
         <property name="total-vfs"
-                  description="The total number of virtual functions to create. Note that when the sriov setting is present NetworkManager enforces the number of virtual functions on the interface (also when it is zero) during activation and resets it upon deactivation. To prevent any changes to SR-IOV parameters don&apos;t add a sriov setting to the connection." />
+                  nmcli-description="The total number of virtual functions to create. Note that when the sriov setting is present NetworkManager enforces the number of virtual functions on the interface (also when it is zero) during activation and resets it upon deactivation. To prevent any changes to SR-IOV parameters don&apos;t add a sriov setting to the connection." />
         <property name="vfs"
-                  description="Array of virtual function descriptors. Each VF descriptor is a dictionary mapping attribute names to GVariant values. The &apos;index&apos; entry is mandatory for each VF. When represented as string a VF is in the form: &quot;INDEX [ATTR=VALUE[ ATTR=VALUE]...]&quot;. for example: &quot;2 mac=00:11:22:33:44:55 spoof-check=true&quot;. Multiple VFs can be specified using a comma as separator. Currently, the following attributes are supported: mac, spoof-check, trust, min-tx-rate, max-tx-rate, vlans. The &quot;vlans&quot; attribute is represented as a semicolon-separated list of VLAN descriptors, where each descriptor has the form &quot;ID[.PRIORITY[.PROTO]]&quot;. PROTO can be either &apos;q&apos; for 802.1Q (the default) or &apos;ad&apos; for 802.1ad." />
+                  nmcli-description="Array of virtual function descriptors. Each VF descriptor is a dictionary mapping attribute names to GVariant values. The &apos;index&apos; entry is mandatory for each VF. When represented as string a VF is in the form: &quot;INDEX [ATTR=VALUE[ ATTR=VALUE]...]&quot;. for example: &quot;2 mac=00:11:22:33:44:55 spoof-check=true&quot;. Multiple VFs can be specified using a comma as separator. Currently, the following attributes are supported: mac, spoof-check, trust, min-tx-rate, max-tx-rate, vlans. The &quot;vlans&quot; attribute is represented as a semicolon-separated list of VLAN descriptors, where each descriptor has the form &quot;ID[.PRIORITY[.PROTO]]&quot;. PROTO can be either &apos;q&apos; for 802.1Q (the default) or &apos;ad&apos; for 802.1ad." />
         <property name="autoprobe-drivers"
-                  description="Whether to autoprobe virtual functions by a compatible driver. If set to NM_TERNARY_TRUE (1), the kernel will try to bind VFs to a compatible driver and if this succeeds a new network interface will be instantiated for each VF. If set to NM_TERNARY_FALSE (0), VFs will not be claimed and no network interfaces will be created for them. When set to NM_TERNARY_DEFAULT (-1), the global default is used; in case the global default is unspecified it is assumed to be NM_TERNARY_TRUE (1)." />
+                  nmcli-description="Whether to autoprobe virtual functions by a compatible driver. If set to NM_TERNARY_TRUE (1), the kernel will try to bind VFs to a compatible driver and if this succeeds a new network interface will be instantiated for each VF. If set to NM_TERNARY_FALSE (0), VFs will not be claimed and no network interfaces will be created for them. When set to NM_TERNARY_DEFAULT (-1), the global default is used; in case the global default is unspecified it is assumed to be NM_TERNARY_TRUE (1)." />
     </setting>
     <setting name="tc" >
         <property name="qdiscs"
-                  description="Array of TC queueing disciplines. When the &quot;tc&quot; setting is present, qdiscs from this property are applied upon activation. If the property is empty, all qdiscs are removed and the device will only have the default qdisc assigned by kernel according to the &quot;net.core.default_qdisc&quot; sysctl. If the &quot;tc&quot; setting is not present, NetworkManager doesn&apos;t touch the qdiscs present on the interface." />
+                  nmcli-description="Array of TC queueing disciplines. When the &quot;tc&quot; setting is present, qdiscs from this property are applied upon activation. If the property is empty, all qdiscs are removed and the device will only have the default qdisc assigned by kernel according to the &quot;net.core.default_qdisc&quot; sysctl. If the &quot;tc&quot; setting is not present, NetworkManager doesn&apos;t touch the qdiscs present on the interface." />
         <property name="tfilters"
-                  description="Array of TC traffic filters. When the &quot;tc&quot; setting is present, filters from this property are applied upon activation. If the property is empty, NetworkManager removes all the filters. If the &quot;tc&quot; setting is not present, NetworkManager doesn&apos;t touch the filters present on the interface." />
+                  nmcli-description="Array of TC traffic filters. When the &quot;tc&quot; setting is present, filters from this property are applied upon activation. If the property is empty, NetworkManager removes all the filters. If the &quot;tc&quot; setting is not present, NetworkManager doesn&apos;t touch the filters present on the interface." />
     </setting>
     <setting name="team" >
         <property name="config"
                   alias="config"
-                  description="The JSON configuration for the team network interface.  The property should contain raw JSON configuration data suitable for teamd, because the value is passed directly to teamd. If not specified, the default configuration is used.  See man teamd.conf for the format details." />
+                  nmcli-description="The JSON configuration for the team network interface.  The property should contain raw JSON configuration data suitable for teamd, because the value is passed directly to teamd. If not specified, the default configuration is used.  See man teamd.conf for the format details." />
         <property name="notify-peers-count"
-                  description="Corresponds to the teamd notify_peers.count." />
+                  nmcli-description="Corresponds to the teamd notify_peers.count." />
         <property name="notify-peers-interval"
-                  description="Corresponds to the teamd notify_peers.interval." />
+                  nmcli-description="Corresponds to the teamd notify_peers.interval." />
         <property name="mcast-rejoin-count"
-                  description="Corresponds to the teamd mcast_rejoin.count." />
+                  nmcli-description="Corresponds to the teamd mcast_rejoin.count." />
         <property name="mcast-rejoin-interval"
-                  description="Corresponds to the teamd mcast_rejoin.interval." />
+                  nmcli-description="Corresponds to the teamd mcast_rejoin.interval." />
         <property name="runner"
-                  description="Corresponds to the teamd runner.name. Permitted values are: &quot;roundrobin&quot;, &quot;broadcast&quot;, &quot;activebackup&quot;, &quot;loadbalance&quot;, &quot;lacp&quot;, &quot;random&quot;." />
+                  nmcli-description="Corresponds to the teamd runner.name. Permitted values are: &quot;roundrobin&quot;, &quot;broadcast&quot;, &quot;activebackup&quot;, &quot;loadbalance&quot;, &quot;lacp&quot;, &quot;random&quot;." />
         <property name="runner-hwaddr-policy"
-                  description="Corresponds to the teamd runner.hwaddr_policy." />
+                  nmcli-description="Corresponds to the teamd runner.hwaddr_policy." />
         <property name="runner-tx-hash"
-                  description="Corresponds to the teamd runner.tx_hash." />
+                  nmcli-description="Corresponds to the teamd runner.tx_hash." />
         <property name="runner-tx-balancer"
-                  description="Corresponds to the teamd runner.tx_balancer.name." />
+                  nmcli-description="Corresponds to the teamd runner.tx_balancer.name." />
         <property name="runner-tx-balancer-interval"
-                  description="Corresponds to the teamd runner.tx_balancer.interval." />
+                  nmcli-description="Corresponds to the teamd runner.tx_balancer.interval." />
         <property name="runner-active"
-                  description="Corresponds to the teamd runner.active." />
+                  nmcli-description="Corresponds to the teamd runner.active." />
         <property name="runner-fast-rate"
-                  description="Corresponds to the teamd runner.fast_rate." />
+                  nmcli-description="Corresponds to the teamd runner.fast_rate." />
         <property name="runner-sys-prio"
-                  description="Corresponds to the teamd runner.sys_prio." />
+                  nmcli-description="Corresponds to the teamd runner.sys_prio." />
         <property name="runner-min-ports"
-                  description="Corresponds to the teamd runner.min_ports." />
+                  nmcli-description="Corresponds to the teamd runner.min_ports." />
         <property name="runner-agg-select-policy"
-                  description="Corresponds to the teamd runner.agg_select_policy." />
+                  nmcli-description="Corresponds to the teamd runner.agg_select_policy." />
         <property name="link-watchers"
-                  description="Link watchers configuration for the connection: each link watcher is defined by a dictionary, whose keys depend upon the selected link watcher. Available link watchers are &apos;ethtool&apos;, &apos;nsna_ping&apos; and &apos;arp_ping&apos; and it is specified in the dictionary with the key &apos;name&apos;. Available keys are:   ethtool: &apos;delay-up&apos;, &apos;delay-down&apos;, &apos;init-wait&apos;; nsna_ping: &apos;init-wait&apos;, &apos;interval&apos;, &apos;missed-max&apos;, &apos;target-host&apos;; arp_ping: all the ones in nsna_ping and &apos;source-host&apos;, &apos;validate-active&apos;, &apos;validate-inactive&apos;, &apos;send-always&apos;. See teamd.conf man for more details." />
+                  nmcli-description="Link watchers configuration for the connection: each link watcher is defined by a dictionary, whose keys depend upon the selected link watcher. Available link watchers are &apos;ethtool&apos;, &apos;nsna_ping&apos; and &apos;arp_ping&apos; and it is specified in the dictionary with the key &apos;name&apos;. Available keys are:   ethtool: &apos;delay-up&apos;, &apos;delay-down&apos;, &apos;init-wait&apos;; nsna_ping: &apos;init-wait&apos;, &apos;interval&apos;, &apos;missed-max&apos;, &apos;target-host&apos;; arp_ping: all the ones in nsna_ping and &apos;source-host&apos;, &apos;validate-active&apos;, &apos;validate-inactive&apos;, &apos;send-always&apos;. See teamd.conf man for more details." />
     </setting>
     <setting name="team-port" >
         <property name="config"
                   alias="config"
-                  description="The JSON configuration for the team port. The property should contain raw JSON configuration data suitable for teamd, because the value is passed directly to teamd. If not specified, the default configuration is used. See man teamd.conf for the format details." />
+                  nmcli-description="The JSON configuration for the team port. The property should contain raw JSON configuration data suitable for teamd, because the value is passed directly to teamd. If not specified, the default configuration is used. See man teamd.conf for the format details." />
         <property name="queue-id"
-                  description="Corresponds to the teamd ports.PORTIFNAME.queue_id. When set to -1 means the parameter is skipped from the json config." />
+                  nmcli-description="Corresponds to the teamd ports.PORTIFNAME.queue_id. When set to -1 means the parameter is skipped from the json config." />
         <property name="prio"
-                  description="Corresponds to the teamd ports.PORTIFNAME.prio." />
+                  nmcli-description="Corresponds to the teamd ports.PORTIFNAME.prio." />
         <property name="sticky"
-                  description="Corresponds to the teamd ports.PORTIFNAME.sticky." />
+                  nmcli-description="Corresponds to the teamd ports.PORTIFNAME.sticky." />
         <property name="lacp-prio"
-                  description="Corresponds to the teamd ports.PORTIFNAME.lacp_prio." />
+                  nmcli-description="Corresponds to the teamd ports.PORTIFNAME.lacp_prio." />
         <property name="lacp-key"
-                  description="Corresponds to the teamd ports.PORTIFNAME.lacp_key." />
+                  nmcli-description="Corresponds to the teamd ports.PORTIFNAME.lacp_key." />
         <property name="link-watchers"
-                  description="Link watchers configuration for the connection: each link watcher is defined by a dictionary, whose keys depend upon the selected link watcher. Available link watchers are &apos;ethtool&apos;, &apos;nsna_ping&apos; and &apos;arp_ping&apos; and it is specified in the dictionary with the key &apos;name&apos;. Available keys are:   ethtool: &apos;delay-up&apos;, &apos;delay-down&apos;, &apos;init-wait&apos;; nsna_ping: &apos;init-wait&apos;, &apos;interval&apos;, &apos;missed-max&apos;, &apos;target-host&apos;; arp_ping: all the ones in nsna_ping and &apos;source-host&apos;, &apos;validate-active&apos;, &apos;validate-inactive&apos;, &apos;send-always&apos;. See teamd.conf man for more details." />
+                  nmcli-description="Link watchers configuration for the connection: each link watcher is defined by a dictionary, whose keys depend upon the selected link watcher. Available link watchers are &apos;ethtool&apos;, &apos;nsna_ping&apos; and &apos;arp_ping&apos; and it is specified in the dictionary with the key &apos;name&apos;. Available keys are:   ethtool: &apos;delay-up&apos;, &apos;delay-down&apos;, &apos;init-wait&apos;; nsna_ping: &apos;init-wait&apos;, &apos;interval&apos;, &apos;missed-max&apos;, &apos;target-host&apos;; arp_ping: all the ones in nsna_ping and &apos;source-host&apos;, &apos;validate-active&apos;, &apos;validate-inactive&apos;, &apos;send-always&apos;. See teamd.conf man for more details." />
     </setting>
     <setting name="tun" >
         <property name="mode"
                   alias="mode"
-                  description="The operating mode of the virtual device. Allowed values are NM_SETTING_TUN_MODE_TUN (1) to create a layer 3 device and NM_SETTING_TUN_MODE_TAP (2) to create an Ethernet-like layer 2 one." />
+                  nmcli-description="The operating mode of the virtual device. Allowed values are NM_SETTING_TUN_MODE_TUN (1) to create a layer 3 device and NM_SETTING_TUN_MODE_TAP (2) to create an Ethernet-like layer 2 one." />
         <property name="owner"
                   alias="owner"
-                  description="The user ID which will own the device. If set to NULL everyone will be able to use the device." />
+                  nmcli-description="The user ID which will own the device. If set to NULL everyone will be able to use the device." />
         <property name="group"
                   alias="group"
-                  description="The group ID which will own the device. If set to NULL everyone will be able to use the device." />
+                  nmcli-description="The group ID which will own the device. If set to NULL everyone will be able to use the device." />
         <property name="pi"
                   alias="pi"
-                  description="If TRUE the interface will prepend a 4 byte header describing the physical interface to the packets." />
+                  nmcli-description="If TRUE the interface will prepend a 4 byte header describing the physical interface to the packets." />
         <property name="vnet-hdr"
                   alias="vnet-hdr"
-                  description="If TRUE the IFF_VNET_HDR the tunnel packets will include a virtio network header." />
+                  nmcli-description="If TRUE the IFF_VNET_HDR the tunnel packets will include a virtio network header." />
         <property name="multi-queue"
                   alias="multi-queue"
-                  description="If the property is set to TRUE, the interface will support multiple file descriptors (queues) to parallelize packet sending or receiving. Otherwise, the interface will only support a single queue." />
+                  nmcli-description="If the property is set to TRUE, the interface will support multiple file descriptors (queues) to parallelize packet sending or receiving. Otherwise, the interface will only support a single queue." />
     </setting>
     <setting name="user" >
     </setting>
     <setting name="veth" >
         <property name="peer"
                   alias="peer"
-                  description="This property specifies the peer interface name of the veth. This property is mandatory." />
+                  nmcli-description="This property specifies the peer interface name of the veth. This property is mandatory." />
     </setting>
     <setting name="vlan" >
         <property name="parent"
                   alias="dev"
-                  description="If given, specifies the parent interface name or parent connection UUID from which this VLAN interface should be created.  If this property is not specified, the connection must contain an &quot;802-3-ethernet&quot; setting with a &quot;mac-address&quot; property." />
+                  nmcli-description="If given, specifies the parent interface name or parent connection UUID from which this VLAN interface should be created.  If this property is not specified, the connection must contain an &quot;802-3-ethernet&quot; setting with a &quot;mac-address&quot; property." />
         <property name="id"
                   alias="id"
-                  description="The VLAN identifier that the interface created by this connection should be assigned. The valid range is from 0 to 4094, without the reserved id 4095." />
+                  nmcli-description="The VLAN identifier that the interface created by this connection should be assigned. The valid range is from 0 to 4094, without the reserved id 4095." />
         <property name="flags"
                   alias="flags"
-                  description="One or more flags which control the behavior and features of the VLAN interface.  Flags include NM_VLAN_FLAG_REORDER_HEADERS (0x1) (reordering of output packet headers), NM_VLAN_FLAG_GVRP (0x2) (use of the GVRP protocol), and NM_VLAN_FLAG_LOOSE_BINDING (0x4) (loose binding of the interface to its master device&apos;s operating state). NM_VLAN_FLAG_MVRP (0x8) (use of the MVRP protocol). The default value of this property is NM_VLAN_FLAG_REORDER_HEADERS, but it used to be 0. To preserve backward compatibility, the default-value in the D-Bus API continues to be 0 and a missing property on D-Bus is still considered as 0." />
+                  nmcli-description="One or more flags which control the behavior and features of the VLAN interface.  Flags include NM_VLAN_FLAG_REORDER_HEADERS (0x1) (reordering of output packet headers), NM_VLAN_FLAG_GVRP (0x2) (use of the GVRP protocol), and NM_VLAN_FLAG_LOOSE_BINDING (0x4) (loose binding of the interface to its master device&apos;s operating state). NM_VLAN_FLAG_MVRP (0x8) (use of the MVRP protocol). The default value of this property is NM_VLAN_FLAG_REORDER_HEADERS, but it used to be 0. To preserve backward compatibility, the default-value in the D-Bus API continues to be 0 and a missing property on D-Bus is still considered as 0." />
         <property name="protocol"
-                  description="Specifies the VLAN protocol to use for encapsulation. Supported values are: &apos;802.1Q&apos;, &apos;802.1ad&apos;. If not specified the default value is &apos;802.1Q&apos;." />
+                  nmcli-description="Specifies the VLAN protocol to use for encapsulation. Supported values are: &apos;802.1Q&apos;, &apos;802.1ad&apos;. If not specified the default value is &apos;802.1Q&apos;." />
         <property name="ingress-priority-map"
                   alias="ingress"
-                  description="For incoming packets, a list of mappings from 802.1p priorities to Linux SKB priorities.  The mapping is given in the format &quot;from:to&quot; where both &quot;from&quot; and &quot;to&quot; are unsigned integers, ie &quot;7:3&quot;." />
+                  nmcli-description="For incoming packets, a list of mappings from 802.1p priorities to Linux SKB priorities.  The mapping is given in the format &quot;from:to&quot; where both &quot;from&quot; and &quot;to&quot; are unsigned integers, ie &quot;7:3&quot;." />
         <property name="egress-priority-map"
                   alias="egress"
-                  description="For outgoing packets, a list of mappings from Linux SKB priorities to 802.1p priorities.  The mapping is given in the format &quot;from:to&quot; where both &quot;from&quot; and &quot;to&quot; are unsigned integers, ie &quot;7:3&quot;." />
+                  nmcli-description="For outgoing packets, a list of mappings from Linux SKB priorities to 802.1p priorities.  The mapping is given in the format &quot;from:to&quot; where both &quot;from&quot; and &quot;to&quot; are unsigned integers, ie &quot;7:3&quot;." />
     </setting>
     <setting name="vpn" >
         <property name="service-type"
                   alias="vpn-type"
-                  description="D-Bus service name of the VPN plugin that this setting uses to connect to its network.  i.e. org.freedesktop.NetworkManager.vpnc for the vpnc plugin." />
+                  nmcli-description="D-Bus service name of the VPN plugin that this setting uses to connect to its network.  i.e. org.freedesktop.NetworkManager.vpnc for the vpnc plugin." />
         <property name="user-name"
                   alias="user"
-                  description="If the VPN connection requires a user name for authentication, that name should be provided here.  If the connection is available to more than one user, and the VPN requires each user to supply a different name, then leave this property empty.  If this property is empty, NetworkManager will automatically supply the username of the user which requested the VPN connection." />
+                  nmcli-description="If the VPN connection requires a user name for authentication, that name should be provided here.  If the connection is available to more than one user, and the VPN requires each user to supply a different name, then leave this property empty.  If this property is empty, NetworkManager will automatically supply the username of the user which requested the VPN connection." />
         <property name="data"
-                  description="Dictionary of key/value pairs of VPN plugin specific data.  Both keys and values must be strings." />
+                  nmcli-description="Dictionary of key/value pairs of VPN plugin specific data.  Both keys and values must be strings." />
         <property name="secrets"
-                  description="Dictionary of key/value pairs of VPN plugin specific secrets like passwords or private keys.  Both keys and values must be strings." />
+                  nmcli-description="Dictionary of key/value pairs of VPN plugin specific secrets like passwords or private keys.  Both keys and values must be strings." />
         <property name="persistent"
-                  description="If the VPN service supports persistence, and this property is TRUE, the VPN will attempt to stay connected across link changes and outages, until explicitly disconnected." />
+                  nmcli-description="If the VPN service supports persistence, and this property is TRUE, the VPN will attempt to stay connected across link changes and outages, until explicitly disconnected." />
         <property name="timeout"
-                  description="Timeout for the VPN service to establish the connection. Some services may take quite a long time to connect. Value of 0 means a default timeout, which is 60 seconds (unless overridden by vpn.timeout in configuration file). Values greater than zero mean timeout in seconds." />
+                  nmcli-description="Timeout for the VPN service to establish the connection. Some services may take quite a long time to connect. Value of 0 means a default timeout, which is 60 seconds (unless overridden by vpn.timeout in configuration file). Values greater than zero mean timeout in seconds." />
     </setting>
     <setting name="vrf" >
         <property name="table"
                   alias="table"
-                  description="The routing table for this VRF." />
+                  nmcli-description="The routing table for this VRF." />
     </setting>
     <setting name="vxlan" >
         <property name="parent"
                   alias="dev"
-                  description="If given, specifies the parent interface name or parent connection UUID." />
+                  nmcli-description="If given, specifies the parent interface name or parent connection UUID." />
         <property name="id"
                   alias="id"
-                  description="Specifies the VXLAN Network Identifier (or VXLAN Segment Identifier) to use." />
+                  nmcli-description="Specifies the VXLAN Network Identifier (or VXLAN Segment Identifier) to use." />
         <property name="local"
                   alias="local"
-                  description="If given, specifies the source IP address to use in outgoing packets." />
+                  nmcli-description="If given, specifies the source IP address to use in outgoing packets." />
         <property name="remote"
                   alias="remote"
-                  description="Specifies the unicast destination IP address to use in outgoing packets when the destination link layer address is not known in the VXLAN device forwarding database, or the multicast IP address to join." />
+                  nmcli-description="Specifies the unicast destination IP address to use in outgoing packets when the destination link layer address is not known in the VXLAN device forwarding database, or the multicast IP address to join." />
         <property name="source-port-min"
                   alias="source-port-min"
-                  description="Specifies the minimum UDP source port to communicate to the remote VXLAN tunnel endpoint." />
+                  nmcli-description="Specifies the minimum UDP source port to communicate to the remote VXLAN tunnel endpoint." />
         <property name="source-port-max"
                   alias="source-port-max"
-                  description="Specifies the maximum UDP source port to communicate to the remote VXLAN tunnel endpoint." />
+                  nmcli-description="Specifies the maximum UDP source port to communicate to the remote VXLAN tunnel endpoint." />
         <property name="destination-port"
                   alias="destination-port"
-                  description="Specifies the UDP destination port to communicate to the remote VXLAN tunnel endpoint." />
+                  nmcli-description="Specifies the UDP destination port to communicate to the remote VXLAN tunnel endpoint." />
         <property name="tos"
-                  description="Specifies the TOS value to use in outgoing packets." />
+                  nmcli-description="Specifies the TOS value to use in outgoing packets." />
         <property name="ttl"
-                  description="Specifies the time-to-live value to use in outgoing packets." />
+                  nmcli-description="Specifies the time-to-live value to use in outgoing packets." />
         <property name="ageing"
-                  description="Specifies the lifetime in seconds of FDB entries learnt by the kernel." />
+                  nmcli-description="Specifies the lifetime in seconds of FDB entries learnt by the kernel." />
         <property name="limit"
-                  description="Specifies the maximum number of FDB entries. A value of zero means that the kernel will store unlimited entries." />
+                  nmcli-description="Specifies the maximum number of FDB entries. A value of zero means that the kernel will store unlimited entries." />
         <property name="learning"
-                  description="Specifies whether unknown source link layer addresses and IP addresses are entered into the VXLAN device forwarding database." />
+                  nmcli-description="Specifies whether unknown source link layer addresses and IP addresses are entered into the VXLAN device forwarding database." />
         <property name="proxy"
-                  description="Specifies whether ARP proxy is turned on." />
+                  nmcli-description="Specifies whether ARP proxy is turned on." />
         <property name="rsc"
-                  description="Specifies whether route short circuit is turned on." />
+                  nmcli-description="Specifies whether route short circuit is turned on." />
         <property name="l2-miss"
-                  description="Specifies whether netlink LL ADDR miss notifications are generated." />
+                  nmcli-description="Specifies whether netlink LL ADDR miss notifications are generated." />
         <property name="l3-miss"
-                  description="Specifies whether netlink IP ADDR miss notifications are generated." />
+                  nmcli-description="Specifies whether netlink IP ADDR miss notifications are generated." />
     </setting>
     <setting name="wifi-p2p" >
         <property name="peer"
                   alias="peer"
-                  description="The P2P device that should be connected to. Currently, this is the only way to create or join a group." />
+                  nmcli-description="The P2P device that should be connected to. Currently, this is the only way to create or join a group." />
         <property name="wps-method"
-                  description="Flags indicating which mode of WPS is to be used. There&apos;s little point in changing the default setting as NetworkManager will automatically determine the best method to use." />
+                  nmcli-description="Flags indicating which mode of WPS is to be used. There&apos;s little point in changing the default setting as NetworkManager will automatically determine the best method to use." />
         <property name="wfd-ies"
-                  description="The Wi-Fi Display (WFD) Information Elements (IEs) to set. Wi-Fi Display requires a protocol specific information element to be set in certain Wi-Fi frames. These can be specified here for the purpose of establishing a connection. This setting is only useful when implementing a Wi-Fi Display client." />
+                  nmcli-description="The Wi-Fi Display (WFD) Information Elements (IEs) to set. Wi-Fi Display requires a protocol specific information element to be set in certain Wi-Fi frames. These can be specified here for the purpose of establishing a connection. This setting is only useful when implementing a Wi-Fi Display client." />
     </setting>
     <setting name="wimax" >
         <property name="mac-address"
                   alias="mac"
-                  description="If specified, this connection will only apply to the WiMAX device whose MAC address matches. This property does not change the MAC address of the device (known as MAC spoofing)." />
+                  nmcli-description="If specified, this connection will only apply to the WiMAX device whose MAC address matches. This property does not change the MAC address of the device (known as MAC spoofing)." />
         <property name="network-name"
                   alias="nsp"
-                  description="Network Service Provider (NSP) name of the WiMAX network this connection should use." />
+                  nmcli-description="Network Service Provider (NSP) name of the WiMAX network this connection should use." />
     </setting>
     <setting name="wireguard" >
         <property name="private-key"
-                  description="The 256 bit private-key in base64 encoding." />
+                  nmcli-description="The 256 bit private-key in base64 encoding." />
         <property name="private-key-flags"
-                  description="Flags indicating how to handle the &quot;private-key&quot; property." />
+                  nmcli-description="Flags indicating how to handle the &quot;private-key&quot; property." />
         <property name="listen-port"
-                  description="The listen-port. If listen-port is not specified, the port will be chosen randomly when the interface comes up." />
+                  nmcli-description="The listen-port. If listen-port is not specified, the port will be chosen randomly when the interface comes up." />
         <property name="fwmark"
-                  description="The use of fwmark is optional and is by default off. Setting it to 0 disables it. Otherwise, it is a 32-bit fwmark for outgoing packets. Note that &quot;ip4-auto-default-route&quot; or &quot;ip6-auto-default-route&quot; enabled, implies to automatically choose a fwmark." />
+                  nmcli-description="The use of fwmark is optional and is by default off. Setting it to 0 disables it. Otherwise, it is a 32-bit fwmark for outgoing packets. Note that &quot;ip4-auto-default-route&quot; or &quot;ip6-auto-default-route&quot; enabled, implies to automatically choose a fwmark." />
         <property name="peer-routes"
-                  description="Whether to automatically add routes for the AllowedIPs ranges of the peers. If TRUE (the default), NetworkManager will automatically add routes in the routing tables according to ipv4.route-table and ipv6.route-table. Usually you want this automatism enabled. If FALSE, no such routes are added automatically. In this case, the user may want to configure static routes in ipv4.routes and ipv6.routes, respectively. Note that if the peer&apos;s AllowedIPs is &quot;0.0.0.0/0&quot; or &quot;::/0&quot; and the profile&apos;s ipv4.never-default or ipv6.never-default setting is enabled, the peer route for this peer won&apos;t be added automatically." />
+                  nmcli-description="Whether to automatically add routes for the AllowedIPs ranges of the peers. If TRUE (the default), NetworkManager will automatically add routes in the routing tables according to ipv4.route-table and ipv6.route-table. Usually you want this automatism enabled. If FALSE, no such routes are added automatically. In this case, the user may want to configure static routes in ipv4.routes and ipv6.routes, respectively. Note that if the peer&apos;s AllowedIPs is &quot;0.0.0.0/0&quot; or &quot;::/0&quot; and the profile&apos;s ipv4.never-default or ipv6.never-default setting is enabled, the peer route for this peer won&apos;t be added automatically." />
         <property name="mtu"
-                  description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple fragments. If zero a default MTU is used. Note that contrary to wg-quick&apos;s MTU setting, this does not take into account the current routes at the time of activation." />
+                  nmcli-description="If non-zero, only transmit packets of the specified size or smaller, breaking larger packets up into multiple fragments. If zero a default MTU is used. Note that contrary to wg-quick&apos;s MTU setting, this does not take into account the current routes at the time of activation." />
         <property name="ip4-auto-default-route"
-                  description="Whether to enable special handling of the IPv4 default route. If enabled, the IPv4 default route from wireguard.peer-routes will be placed to a dedicated routing-table and two policy routing rules will be added. The fwmark number is also used as routing-table for the default-route, and if fwmark is zero, an unused fwmark/table is chosen automatically. This corresponds to what wg-quick does with Table=auto and what WireGuard calls &quot;Improved Rule-based Routing&quot;. Note that for this automatism to work, you usually don&apos;t want to set ipv4.gateway, because that will result in a conflicting default route. Leaving this at the default will enable this option automatically if ipv4.never-default is not set and there are any peers that use a default-route as allowed-ips. Since this automatism only makes sense if you also have a peer with an /0 allowed-ips, it is usually not necessary to enable this explicitly. However, you can disable it if you want to configure your own routing and rules." />
+                  nmcli-description="Whether to enable special handling of the IPv4 default route. If enabled, the IPv4 default route from wireguard.peer-routes will be placed to a dedicated routing-table and two policy routing rules will be added. The fwmark number is also used as routing-table for the default-route, and if fwmark is zero, an unused fwmark/table is chosen automatically. This corresponds to what wg-quick does with Table=auto and what WireGuard calls &quot;Improved Rule-based Routing&quot;. Note that for this automatism to work, you usually don&apos;t want to set ipv4.gateway, because that will result in a conflicting default route. Leaving this at the default will enable this option automatically if ipv4.never-default is not set and there are any peers that use a default-route as allowed-ips. Since this automatism only makes sense if you also have a peer with an /0 allowed-ips, it is usually not necessary to enable this explicitly. However, you can disable it if you want to configure your own routing and rules." />
         <property name="ip6-auto-default-route"
-                  description="Like ip4-auto-default-route, but for the IPv6 default route." />
+                  nmcli-description="Like ip4-auto-default-route, but for the IPv6 default route." />
     </setting>
     <setting name="wpan" >
         <property name="mac-address"
                   alias="mac"
-                  description="If specified, this connection will only apply to the IEEE 802.15.4 (WPAN) MAC layer device whose permanent MAC address matches." />
+                  nmcli-description="If specified, this connection will only apply to the IEEE 802.15.4 (WPAN) MAC layer device whose permanent MAC address matches." />
         <property name="short-address"
                   alias="short-addr"
-                  description="Short IEEE 802.15.4 address to be used within a restricted environment." />
+                  nmcli-description="Short IEEE 802.15.4 address to be used within a restricted environment." />
         <property name="pan-id"
                   alias="pan-id"
-                  description="IEEE 802.15.4 Personal Area Network (PAN) identifier." />
+                  nmcli-description="IEEE 802.15.4 Personal Area Network (PAN) identifier." />
         <property name="page"
                   alias="page"
-                  description="IEEE 802.15.4 channel page. A positive integer or -1, meaning &quot;do not set, use whatever the device is already set to&quot;." />
+                  nmcli-description="IEEE 802.15.4 channel page. A positive integer or -1, meaning &quot;do not set, use whatever the device is already set to&quot;." />
         <property name="channel"
                   alias="channel"
-                  description="IEEE 802.15.4 channel. A positive integer or -1, meaning &quot;do not set, use whatever the device is already set to&quot;." />
+                  nmcli-description="IEEE 802.15.4 channel. A positive integer or -1, meaning &quot;do not set, use whatever the device is already set to&quot;." />
     </setting>
 </nm-setting-docs>
diff --git a/src/nmcli/general.c b/src/nmcli/general.c
index 26988cd8..0d8ddc47 100644
--- a/src/nmcli/general.c
+++ b/src/nmcli/general.c
@@ -1415,11 +1415,11 @@ ac_overview(NmCli *nmc, NMActiveConnection *ac)
     NMIPConfig              *ip;
     nm_auto_str_buf NMStrBuf str = NM_STR_BUF_INIT(NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE);
 
-    if (nm_active_connection_get_master(ac)) {
+    if (nm_active_connection_get_controller(ac)) {
         g_string_append_printf(outbuf,
                                "%s %s, ",
                                _("master"),
-                               nm_device_get_iface(nm_active_connection_get_master(ac)));
+                               nm_device_get_iface(nm_active_connection_get_controller(ac)));
     }
     if (nm_active_connection_get_vpn(ac))
         g_string_append_printf(outbuf, "%s, ", _("VPN"));
diff --git a/src/nmcli/nmcli.c b/src/nmcli/nmcli.c
index ff496874..792edf6e 100644
--- a/src/nmcli/nmcli.c
+++ b/src/nmcli/nmcli.c
@@ -785,7 +785,7 @@ process_command_line(NmCli *nmc, int argc, char **argv_orig)
         if (matches_arg(nmc, &argc, &argv, "-overview", NULL)) {
             nmc->nmc_config_mutable.overview = TRUE;
         } else if (matches_arg(nmc, &argc, &argv, "-offline", NULL)) {
-            nmc->offline = TRUE;
+            nmc->nmc_config_mutable.offline = TRUE;
         } else if (matches_arg(nmc, &argc, &argv, "-terse", NULL)) {
             if (nmc->nmc_config.print_output == NMC_PRINT_TERSE) {
                 g_string_printf(nmc->return_text,
diff --git a/src/nmcli/nmcli.h b/src/nmcli/nmcli.h
index f9b4cc7d..e704c76f 100644
--- a/src/nmcli/nmcli.h
+++ b/src/nmcli/nmcli.h
@@ -132,6 +132,9 @@ typedef struct _NmcConfig {
     /* Overview mode (hide default values) */
     bool overview : 1;
 
+    /* Communicate the connection data over stdin/stdout instead of talking to the daemon. */
+    bool offline : 1;
+
     NmcColorPalette palette;
 } NmcConfig;
 
@@ -178,9 +181,6 @@ typedef struct _NmCli {
     /* Whether tabular/multiline mode was specified via '--mode' option */
     bool mode_specified : 1;
 
-    /* Communicate the connection data over stdin/stdout instead of talking to the daemon. */
-    bool offline : 1;
-
     /* Ask for missing parameters: option '--ask' */
     bool ask : 1;
 
diff --git a/src/nmcli/settings.c b/src/nmcli/settings.c
index 3c1f3c38..5b818edb 100644
--- a/src/nmcli/settings.c
+++ b/src/nmcli/settings.c
@@ -254,7 +254,7 @@ wireless_band_channel_changed_cb(GObject *object, GParamSpec *pspec, gpointer us
 }
 
 static void
-connection_master_changed_cb(GObject *object, GParamSpec *pspec, gpointer user_data)
+connection_controller_changed_cb(GObject *object, GParamSpec *pspec, gpointer user_data)
 {
     NMSettingConnection *s_con      = NM_SETTING_CONNECTION(object);
     NMConnection        *connection = NM_CONNECTION(user_data);
@@ -343,7 +343,7 @@ nmc_setting_connection_connect_handlers(NMSettingConnection *setting, NMConnecti
 
     g_signal_connect(setting,
                      "notify::" NM_SETTING_CONNECTION_MASTER,
-                     G_CALLBACK(connection_master_changed_cb),
+                     G_CALLBACK(connection_controller_changed_cb),
                      connection);
 }
 
@@ -366,6 +366,9 @@ _set_fcn_precheck_connection_secondaries(NMClient   *client,
     if (!strv0)
         return TRUE;
 
+    if (!client)
+        return TRUE;
+
     connections = nm_client_get_connections(client);
 
     strv = g_strdupv((char **) strv0);
@@ -495,7 +498,7 @@ _env_get_env_flags(const NMMetaEnvironment *environment, gpointer environment_us
 
     nm_assert(nmc);
 
-    return (nmc->offline ? NM_META_ENV_FLAGS_OFFLINE : NM_META_ENV_FLAGS_NONE);
+    return (nmc->nmc_config.offline ? NM_META_ENV_FLAGS_OFFLINE : NM_META_ENV_FLAGS_NONE);
 }
 
 /*****************************************************************************/
diff --git a/src/nmcli/utils.c b/src/nmcli/utils.c
index 2c155fff..29e84cc2 100644
--- a/src/nmcli/utils.c
+++ b/src/nmcli/utils.c
@@ -837,8 +837,8 @@ _output_selection_parse(const NMMetaAbstractInfo *const *fields,
  * @field_str: comma-separated field names to parse
  * @fields_array: array of allowed fields
  * @parse_groups: whether the fields can contain group prefix (e.g. general.driver)
- * @group_fields: (out) (allow-none): array of field names for particular groups
- * @error: (out) (allow-none): location to store error, or %NULL
+ * @group_fields: (out) (optional) (nullable): array of field names for particular groups
+ * @error: location to store error, or %NULL
  *
  * Parses comma separated fields in @fields_str according to @fields_array.
  * When @parse_groups is %TRUE, fields can be in the form 'group.field'. Then
@@ -1467,6 +1467,7 @@ nmc_terminal_spawn_pager(const NmcConfig *nmc_config)
     ev = g_get_environ();
     ev = g_environ_setenv(ev, "LESS", "FRSXMK", TRUE);
     ev = g_environ_setenv(ev, "LESSCHARSET", "utf-8", TRUE);
+    ev = g_environ_setenv(ev, "LESSSECURE", "1", FALSE);
 
     pager_pid = fork();
     if (pager_pid == -1) {
@@ -1821,3 +1822,19 @@ print_data(const NmcConfig     *nmc_config,
                               field_values);
     }
 }
+
+void
+nmc_warn_if_version_mismatch(NMClient *client)
+{
+    const char *nm_ver;
+
+    g_return_if_fail(client != NULL);
+
+    nm_ver = nm_client_get_version(client);
+    if (!nm_streq0(nm_ver, VERSION)) {
+        g_printerr(_("Warning: nmcli (%s) and NetworkManager (%s) versions don't match. "
+                     "Restarting NetworkManager is advised.\n"),
+                   VERSION,
+                   nm_ver ? nm_ver : _("Unknown"));
+    }
+}
diff --git a/src/nmcli/utils.h b/src/nmcli/utils.h
index 5d9449f5..0960c45c 100644
--- a/src/nmcli/utils.h
+++ b/src/nmcli/utils.h
@@ -72,6 +72,8 @@ void            print_data(const NmcConfig     *nmc_config,
                            int                  indent,
                            const NmcOutputData *out);
 
+void nmc_warn_if_version_mismatch(NMClient *client);
+
 /*****************************************************************************/
 
 extern const NMMetaEnvironment *const nmc_meta_environment;
@@ -134,7 +136,7 @@ typedef enum {
     NMC_GENERIC_INFO_TYPE_CON_SHOW_DEVICE,
     NMC_GENERIC_INFO_TYPE_CON_SHOW_STATE,
     NMC_GENERIC_INFO_TYPE_CON_SHOW_ACTIVE_PATH,
-    NMC_GENERIC_INFO_TYPE_CON_SHOW_SLAVE,
+    NMC_GENERIC_INFO_TYPE_CON_SHOW_PORT,
     NMC_GENERIC_INFO_TYPE_CON_SHOW_FILENAME,
     _NMC_GENERIC_INFO_TYPE_CON_SHOW_NUM,
 
@@ -150,7 +152,7 @@ typedef enum {
     NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_DBUS_PATH,
     NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CON_PATH,
     NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_ZONE,
-    NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_MASTER_PATH,
+    NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_CONTROLLER_PATH,
     _NMC_GENERIC_INFO_TYPE_CON_ACTIVE_GENERAL_NUM,
 
     NMC_GENERIC_INFO_TYPE_CON_VPN_TYPE = 0,
diff --git a/src/nmtui/meson.build b/src/nmtui/meson.build
index 48f2fbf4..13aa8647 100644
--- a/src/nmtui/meson.build
+++ b/src/nmtui/meson.build
@@ -40,13 +40,14 @@ executable(
     'nmt-route-editor.c',
     'nmt-route-entry.c',
     'nmt-route-table.c',
-    'nmt-slave-list.c',
+    'nmt-port-list.c',
     'nmt-wireguard-peer-list.c',
     'nmt-wireguard-peer-editor.c',
     'nmtui.c',
     'nmtui-connect.c',
     'nmtui-edit.c',
     'nmtui-hostname.c',
+    'nmtui-radio.c',
     'nmt-utils.c',
     'nmt-widget-list.c',
   ),
diff --git a/src/nmtui/nm-editor-utils.c b/src/nmtui/nm-editor-utils.c
index 0f69c962..718c0aef 100644
--- a/src/nmtui/nm-editor-utils.c
+++ b/src/nmtui/nm-editor-utils.c
@@ -201,22 +201,22 @@ nm_editor_utils_get_connection_type_list(void)
     item->connection_setup_func = bond_connection_setup_func;
     g_ptr_array_add(array, item);
 
-    item                          = g_new0(NMEditorConnectionTypeDataReal, 1);
-    item->data.name               = _("Bridge");
-    item->data.setting_type       = NM_TYPE_SETTING_BRIDGE;
-    item->data.slave_setting_type = NM_TYPE_SETTING_BRIDGE_PORT;
-    item->data.device_type        = NM_TYPE_DEVICE_BRIDGE;
-    item->data.virtual            = TRUE;
-    item->id_format               = _("Bridge connection %d");
+    item                         = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name              = _("Bridge");
+    item->data.setting_type      = NM_TYPE_SETTING_BRIDGE;
+    item->data.port_setting_type = NM_TYPE_SETTING_BRIDGE_PORT;
+    item->data.device_type       = NM_TYPE_DEVICE_BRIDGE;
+    item->data.virtual           = TRUE;
+    item->id_format              = _("Bridge connection %d");
     g_ptr_array_add(array, item);
 
-    item                          = g_new0(NMEditorConnectionTypeDataReal, 1);
-    item->data.name               = _("Team");
-    item->data.setting_type       = NM_TYPE_SETTING_TEAM;
-    item->data.slave_setting_type = NM_TYPE_SETTING_TEAM_PORT;
-    item->data.device_type        = NM_TYPE_DEVICE_TEAM;
-    item->data.virtual            = TRUE;
-    item->id_format               = _("Team connection %d");
+    item                         = g_new0(NMEditorConnectionTypeDataReal, 1);
+    item->data.name              = _("Team");
+    item->data.setting_type      = NM_TYPE_SETTING_TEAM;
+    item->data.port_setting_type = NM_TYPE_SETTING_TEAM_PORT;
+    item->data.device_type       = NM_TYPE_DEVICE_TEAM;
+    item->data.virtual           = TRUE;
+    item->id_format              = _("Team connection %d");
     g_ptr_array_add(array, item);
 
     item                    = g_new0(NMEditorConnectionTypeDataReal, 1);
@@ -367,46 +367,46 @@ get_available_iface_name(const char *try_name, NMClient *client)
 /**
  * nm_editor_utils_create_connection:
  * @type: the type of the connection's primary #NMSetting
- * @master: (allow-none): the connection's master, if any
+ * @controller: (nullable): the connection's controller, if any
  * @client: an #NMClient
  *
  * Creates a new #NMConnection of the given type, automatically
  * creating a UUID and an appropriate not-currently-in-use connection
  * name, setting #NMSettingConnection:autoconnect appropriately for
- * the connection type, filling in slave-related information if
- * @master is not %NULL, and initializing any other mandatory-to-set
+ * the connection type, filling in port-related information if
+ * @controller is not %NULL, and initializing any other mandatory-to-set
  * properties to reasonable initial values.
  *
  * Returns: a new #NMConnection
  */
 NMConnection *
-nm_editor_utils_create_connection(GType type, NMConnection *master, NMClient *client)
+nm_editor_utils_create_connection(GType type, NMConnection *controller, NMClient *client)
 {
     NMEditorConnectionTypeData    **types;
-    NMEditorConnectionTypeDataReal *type_data           = NULL;
-    const char                     *master_setting_type = NULL, *master_uuid = NULL;
-    GType                master_type = G_TYPE_INVALID, slave_setting_type = G_TYPE_INVALID;
+    NMEditorConnectionTypeDataReal *type_data               = NULL;
+    const char                     *controller_setting_type = NULL, *controller_uuid = NULL;
+    GType                controller_type = G_TYPE_INVALID, port_setting_type = G_TYPE_INVALID;
     NMConnection        *connection;
     NMSettingConnection *s_con;
-    NMSetting           *s_hw, *s_slave;
+    NMSetting           *s_hw, *s_port;
     char                *uuid, *id, *ifname;
     int                  i;
 
-    if (master) {
-        NMSettingConnection *master_s_con;
+    if (controller) {
+        NMSettingConnection *controller_s_con;
 
-        master_s_con        = nm_connection_get_setting_connection(master);
-        master_setting_type = nm_setting_connection_get_connection_type(master_s_con);
-        master_uuid         = nm_setting_connection_get_uuid(master_s_con);
-        master_type         = nm_setting_lookup_type(master_setting_type);
+        controller_s_con        = nm_connection_get_setting_connection(controller);
+        controller_setting_type = nm_setting_connection_get_connection_type(controller_s_con);
+        controller_uuid         = nm_setting_connection_get_uuid(controller_s_con);
+        controller_type         = nm_setting_lookup_type(controller_setting_type);
     }
 
     types = nm_editor_utils_get_connection_type_list();
     for (i = 0; types[i]; i++) {
         if (types[i]->setting_type == type)
             type_data = (NMEditorConnectionTypeDataReal *) types[i];
-        if (types[i]->setting_type == master_type)
-            slave_setting_type = types[i]->slave_setting_type;
+        if (types[i]->setting_type == controller_type)
+            port_setting_type = types[i]->port_setting_type;
     }
     if (!type_data) {
         g_return_val_if_reached(NULL);
@@ -430,9 +430,9 @@ nm_editor_utils_create_connection(GType type, NMConnection *master, NMClient *cl
     else
         ifname = NULL;
 
-    if (slave_setting_type != G_TYPE_INVALID) {
-        s_slave = g_object_new(slave_setting_type, NULL);
-        nm_connection_add_setting(connection, s_slave);
+    if (port_setting_type != G_TYPE_INVALID) {
+        s_port = g_object_new(port_setting_type, NULL);
+        nm_connection_add_setting(connection, s_port);
     }
 
     uuid = nm_utils_uuid_generate();
@@ -448,9 +448,9 @@ nm_editor_utils_create_connection(GType type, NMConnection *master, NMClient *cl
                  NM_SETTING_CONNECTION_AUTOCONNECT,
                  !type_data->no_autoconnect,
                  NM_SETTING_CONNECTION_MASTER,
-                 master_uuid,
+                 controller_uuid,
                  NM_SETTING_CONNECTION_SLAVE_TYPE,
-                 master_setting_type,
+                 controller_setting_type,
                  NM_SETTING_CONNECTION_INTERFACE_NAME,
                  ifname,
                  NULL);
diff --git a/src/nmtui/nm-editor-utils.h b/src/nmtui/nm-editor-utils.h
index decbc271..98f59595 100644
--- a/src/nmtui/nm-editor-utils.h
+++ b/src/nmtui/nm-editor-utils.h
@@ -9,7 +9,7 @@
 typedef struct {
     const char *name;
     GType       setting_type;
-    GType       slave_setting_type;
+    GType       port_setting_type;
     GType       device_type;
     gboolean virtual;
 } NMEditorConnectionTypeData;
@@ -17,6 +17,7 @@ typedef struct {
 NMEditorConnectionTypeData **nm_editor_utils_get_connection_type_list(void);
 NMEditorConnectionTypeData  *nm_editor_utils_get_connection_type_data(NMConnection *conn);
 
-NMConnection *nm_editor_utils_create_connection(GType type, NMConnection *master, NMClient *client);
+NMConnection *
+nm_editor_utils_create_connection(GType type, NMConnection *controller, NMClient *client);
 
 #endif /* NM_EDITOR_UTILS_H */
diff --git a/src/nmtui/nmt-edit-connection-list.c b/src/nmtui/nmt-edit-connection-list.c
index aec669c8..f8d8303b 100644
--- a/src/nmtui/nmt-edit-connection-list.c
+++ b/src/nmtui/nmt-edit-connection-list.c
@@ -490,7 +490,7 @@ nmt_edit_connection_list_class_init(NmtEditConnectionListClass *list_class)
      *
      * If %TRUE, connections should be grouped by type, with headers
      * indicating the types (as in the main connection list). If %FALSE,
-     * they will not be grouped (as in slave connection lists).
+     * they will not be grouped (as in port connection lists).
      */
     g_object_class_install_property(
         object_class,
diff --git a/src/nmtui/nmt-editor-grid.c b/src/nmtui/nmt-editor-grid.c
index e3524084..9b76e3fe 100644
--- a/src/nmtui/nmt-editor-grid.c
+++ b/src/nmtui/nmt-editor-grid.c
@@ -85,9 +85,9 @@ nmt_editor_grid_finalize(GObject *object)
 /**
  * nmt_editor_grid_append:
  * @grid: the #NmtEditorGrid
- * @label: (allow-none): the label text for @widget, or %NULL
- * @widget: (allow-none): the (main) widget
- * @extra: (allow-none): optional extra widget
+ * @label: (nullable): the label text for @widget, or %NULL
+ * @widget: (nullable): the (main) widget
+ * @extra: (nullable): optional extra widget
  *
  * Adds a row to @grid.
  *
diff --git a/src/nmtui/nmt-editor-page.c b/src/nmtui/nmt-editor-page.c
index d94b1f0f..7af277fc 100644
--- a/src/nmtui/nmt-editor-page.c
+++ b/src/nmtui/nmt-editor-page.c
@@ -104,7 +104,7 @@ nmt_editor_page_add_section(NmtEditorPage *page, NmtEditorSection *section)
  *
  * This method is called when the user saves the connection. It gives
  * the page a chance to do save its data outside the connections (such as
- * recommit the slave connections).
+ * recommit the port connections).
  */
 void
 nmt_editor_page_saved(NmtEditorPage *page)
diff --git a/src/nmtui/nmt-editor-section.c b/src/nmtui/nmt-editor-section.c
index 462d1f89..0f8aa6e0 100644
--- a/src/nmtui/nmt-editor-section.c
+++ b/src/nmtui/nmt-editor-section.c
@@ -42,7 +42,7 @@ enum {
 /**
  * nmt_editor_section_new:
  * @title: the section title
- * @header_widget: (allow-none): the widget to show next to the title
+ * @header_widget: (nullable): the widget to show next to the title
  * @show_by_default: whether the section should be open by default
  *
  * Creates a new #NmtEditorSection.
diff --git a/src/nmtui/nmt-editor.c b/src/nmtui/nmt-editor.c
index 9a64333a..ad8803b6 100644
--- a/src/nmtui/nmt-editor.c
+++ b/src/nmtui/nmt-editor.c
@@ -306,7 +306,7 @@ nmt_editor_constructed(GObject *object)
     const char          *deventry_label;
     NmtDeviceEntry      *deventry;
     GType                hardware_type;
-    const char          *slave_type;
+    const char          *port_type;
     NmtEditorPage       *page;
 
     if (G_OBJECT_CLASS(nmt_editor_parent_class)->constructed)
@@ -385,13 +385,13 @@ nmt_editor_constructed(GObject *object)
     add_sections_for_page(editor, grid, page);
     nmt_editor_grid_append(grid, NULL, nmt_newt_separator_new(), NULL);
 
-    slave_type = nm_setting_connection_get_slave_type(s_con);
-    if (slave_type) {
-        if (!strcmp(slave_type, NM_SETTING_BRIDGE_SETTING_NAME))
+    port_type = nm_setting_connection_get_slave_type(s_con);
+    if (port_type) {
+        if (!strcmp(port_type, NM_SETTING_BRIDGE_SETTING_NAME))
             add_sections_for_page(editor, grid, nmt_page_bridge_port_new(priv->edit_connection));
-        else if (!strcmp(slave_type, NM_SETTING_TEAM_SETTING_NAME))
+        else if (!strcmp(port_type, NM_SETTING_TEAM_SETTING_NAME))
             add_sections_for_page(editor, grid, nmt_page_team_port_new(priv->edit_connection));
-        else if (nm_streq(slave_type, NM_SETTING_BOND_SETTING_NAME))
+        else if (nm_streq(port_type, NM_SETTING_BOND_SETTING_NAME))
             add_sections_for_page(editor, grid, nmt_page_bond_port_new(priv->edit_connection));
     } else {
         NmtNewtWidget *section;
diff --git a/src/nmtui/nmt-page-bond.c b/src/nmtui/nmt-page-bond.c
index edcf681e..5a64590c 100644
--- a/src/nmtui/nmt-page-bond.c
+++ b/src/nmtui/nmt-page-bond.c
@@ -22,7 +22,7 @@
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-mac-entry.h"
 #include "nmt-address-list.h"
-#include "nmt-slave-list.h"
+#include "nmt-port-list.h"
 
 G_DEFINE_TYPE(NmtPageBond, nmt_page_bond, NMT_TYPE_EDITOR_PAGE_DEVICE)
 
@@ -36,7 +36,7 @@ typedef enum {
 } NmtPageBondMonitoringMode;
 
 typedef struct {
-    NmtSlaveList *slaves;
+    NmtPortList *ports;
 
     /* Note: when adding new options to the UI also ensure they are
      * initialized in bond_connection_setup_func()
@@ -53,7 +53,7 @@ typedef struct {
     NmtPageBondMonitoringMode monitoring_mode;
 
     NMSettingBond *s_bond;
-    GType          slave_type;
+    GType          port_type;
     gboolean       updating;
 } NmtPageBondPrivate;
 
@@ -75,7 +75,7 @@ nmt_page_bond_init(NmtPageBond *bond)
     NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
 
     priv->monitoring_mode = NMT_PAGE_BOND_MONITORING_UNKNOWN;
-    priv->slave_type      = G_TYPE_NONE;
+    priv->port_type       = G_TYPE_NONE;
 }
 
 static NmtNewtPopupEntry bond_mode[] = {
@@ -141,7 +141,7 @@ bond_options_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
     nmt_newt_entry_set_text(priv->arp_interval, val ?: "0");
 
     val = nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_ARP_IP_TARGET);
-    ips = nm_utils_bond_option_arp_ip_targets_split(val);
+    ips = nm_utils_bond_option_ip_split(val);
     g_object_set(G_OBJECT(priv->arp_ip_target),
                  "strings",
                  ips ?: NM_PTRARRAY_EMPTY(const char *),
@@ -159,30 +159,30 @@ bond_options_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
 }
 
 static void
-slaves_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+ports_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
 {
     NmtPageBond        *bond = NMT_PAGE_BOND(user_data);
     NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
-    GPtrArray          *slaves;
+    GPtrArray          *ports;
 
-    g_object_get(object, "connections", &slaves, NULL);
-    if (slaves->len == 0) {
-        if (priv->slave_type == G_TYPE_NONE)
+    g_object_get(object, "connections", &ports, NULL);
+    if (ports->len == 0) {
+        if (priv->port_type == G_TYPE_NONE)
             return;
-        priv->slave_type = G_TYPE_NONE;
+        priv->port_type = G_TYPE_NONE;
     } else {
-        NMConnection *slave = slaves->pdata[0];
+        NMConnection *port = ports->pdata[0];
 
-        if (priv->slave_type != G_TYPE_NONE)
+        if (priv->port_type != G_TYPE_NONE)
             return;
 
-        if (nm_connection_is_type(slave, NM_SETTING_INFINIBAND_SETTING_NAME))
-            priv->slave_type = NM_TYPE_SETTING_INFINIBAND;
+        if (nm_connection_is_type(port, NM_SETTING_INFINIBAND_SETTING_NAME))
+            priv->port_type = NM_TYPE_SETTING_INFINIBAND;
         else
-            priv->slave_type = NM_TYPE_SETTING_WIRED;
+            priv->port_type = NM_TYPE_SETTING_WIRED;
     }
 
-    if (priv->slave_type == NM_TYPE_SETTING_INFINIBAND) {
+    if (priv->port_type == NM_TYPE_SETTING_INFINIBAND) {
         nmt_newt_popup_set_active_id(priv->mode, "active-backup");
         nmt_newt_component_set_sensitive(NMT_NEWT_COMPONENT(priv->mode), FALSE);
     } else
@@ -314,9 +314,9 @@ bond_connection_type_filter(GType connection_type, gpointer user_data)
     NmtPageBond        *bond = user_data;
     NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(bond);
 
-    if (priv->slave_type != NM_TYPE_SETTING_WIRED && connection_type == NM_TYPE_SETTING_INFINIBAND)
+    if (priv->port_type != NM_TYPE_SETTING_WIRED && connection_type == NM_TYPE_SETTING_INFINIBAND)
         return TRUE;
-    if (priv->slave_type != NM_TYPE_SETTING_INFINIBAND && connection_type == NM_TYPE_SETTING_WIRED)
+    if (priv->port_type != NM_TYPE_SETTING_INFINIBAND && connection_type == NM_TYPE_SETTING_WIRED)
         return TRUE;
 
     return FALSE;
@@ -346,10 +346,10 @@ nmt_page_bond_constructed(GObject *object)
     nmt_editor_grid_append(grid, _("Slaves"), widget, NULL);
     nmt_editor_grid_set_row_flags(grid, widget, NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT);
 
-    widget = nmt_slave_list_new(conn, bond_connection_type_filter, bond);
-    g_signal_connect(widget, "notify::connections", G_CALLBACK(slaves_changed), bond);
+    widget = nmt_port_list_new(conn, bond_connection_type_filter, bond);
+    g_signal_connect(widget, "notify::connections", G_CALLBACK(ports_changed), bond);
     nmt_editor_grid_append(grid, NULL, widget, NULL);
-    priv->slaves = NMT_SLAVE_LIST(widget);
+    priv->ports = NMT_PORT_LIST(widget);
 
     widget = nmt_newt_popup_new(bond_mode);
     g_signal_connect(widget, "notify::active-id", G_CALLBACK(mode_widget_changed), bond);
@@ -408,7 +408,7 @@ nmt_page_bond_constructed(GObject *object)
                      G_CALLBACK(bond_options_changed),
                      bond);
     bond_options_changed(G_OBJECT(s_bond), NULL, bond);
-    slaves_changed(G_OBJECT(priv->slaves), NULL, bond);
+    ports_changed(G_OBJECT(priv->ports), NULL, bond);
 
     nmt_editor_page_add_section(NMT_EDITOR_PAGE(bond), section);
 
@@ -420,7 +420,7 @@ nmt_page_bond_saved(NmtEditorPage *editor_page)
 {
     NmtPageBondPrivate *priv = NMT_PAGE_BOND_GET_PRIVATE(editor_page);
 
-    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->slaves));
+    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->ports));
 }
 
 static void
diff --git a/src/nmtui/nmt-page-bridge.c b/src/nmtui/nmt-page-bridge.c
index bc32e2a2..e84af1d8 100644
--- a/src/nmtui/nmt-page-bridge.c
+++ b/src/nmtui/nmt-page-bridge.c
@@ -14,7 +14,7 @@
 
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nmt-address-list.h"
-#include "nmt-slave-list.h"
+#include "nmt-port-list.h"
 
 G_DEFINE_TYPE(NmtPageBridge, nmt_page_bridge, NMT_TYPE_EDITOR_PAGE_DEVICE)
 
@@ -22,7 +22,7 @@ G_DEFINE_TYPE(NmtPageBridge, nmt_page_bridge, NMT_TYPE_EDITOR_PAGE_DEVICE)
     (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_BRIDGE, NmtPageBridgePrivate))
 
 typedef struct {
-    NmtSlaveList *slaves;
+    NmtPortList *ports;
 } NmtPageBridgePrivate;
 
 NmtEditorPage *
@@ -63,9 +63,9 @@ nmt_page_bridge_constructed(GObject *object)
     nmt_editor_grid_append(grid, _("Slaves"), widget, NULL);
     nmt_editor_grid_set_row_flags(grid, widget, NMT_EDITOR_GRID_ROW_LABEL_ALIGN_LEFT);
 
-    widget = nmt_slave_list_new(conn, bridge_connection_type_filter, bridge);
+    widget = nmt_port_list_new(conn, bridge_connection_type_filter, bridge);
     nmt_editor_grid_append(grid, NULL, widget, NULL);
-    priv->slaves = NMT_SLAVE_LIST(widget);
+    priv->ports = NMT_PORT_LIST(widget);
 
     widget = nmt_newt_entry_numeric_new(10, 0, 1000000);
     g_object_bind_property(s_bridge,
@@ -165,7 +165,7 @@ nmt_page_bridge_saved(NmtEditorPage *editor_page)
 {
     NmtPageBridgePrivate *priv = NMT_PAGE_BRIDGE_GET_PRIVATE(editor_page);
 
-    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->slaves));
+    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->ports));
 }
 
 static void
diff --git a/src/nmtui/nmt-page-team.c b/src/nmtui/nmt-page-team.c
index 1a554aad..f6311c7e 100644
--- a/src/nmtui/nmt-page-team.c
+++ b/src/nmtui/nmt-page-team.c
@@ -13,7 +13,7 @@
 #include "nmt-page-team.h"
 
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
-#include "nmt-slave-list.h"
+#include "nmt-port-list.h"
 
 G_DEFINE_TYPE(NmtPageTeam, nmt_page_team, NMT_TYPE_EDITOR_PAGE_DEVICE)
 
@@ -21,10 +21,10 @@ G_DEFINE_TYPE(NmtPageTeam, nmt_page_team, NMT_TYPE_EDITOR_PAGE_DEVICE)
     (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PAGE_TEAM, NmtPageTeamPrivate))
 
 typedef struct {
-    NmtSlaveList *slaves;
+    NmtPortList *ports;
 
     NMSettingTeam *s_team;
-    GType          slave_type;
+    GType          port_type;
 
 } NmtPageTeamPrivate;
 
@@ -39,26 +39,26 @@ nmt_page_team_init(NmtPageTeam *team)
 {
     NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(team);
 
-    priv->slave_type = G_TYPE_NONE;
+    priv->port_type = G_TYPE_NONE;
 }
 
 static void
-slaves_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
+ports_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
 {
     NmtPageTeam        *team = NMT_PAGE_TEAM(user_data);
     NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(team);
-    GPtrArray          *slaves;
+    GPtrArray          *ports;
 
-    g_object_get(object, "connections", &slaves, NULL);
-    if (slaves->len == 0) {
-        priv->slave_type = G_TYPE_NONE;
-    } else if (priv->slave_type == G_TYPE_NONE) {
-        NMConnection *slave = slaves->pdata[0];
+    g_object_get(object, "connections", &ports, NULL);
+    if (ports->len == 0) {
+        priv->port_type = G_TYPE_NONE;
+    } else if (priv->port_type == G_TYPE_NONE) {
+        NMConnection *port = ports->pdata[0];
 
-        if (nm_connection_is_type(slave, NM_SETTING_INFINIBAND_SETTING_NAME))
-            priv->slave_type = NM_TYPE_SETTING_INFINIBAND;
+        if (nm_connection_is_type(port, NM_SETTING_INFINIBAND_SETTING_NAME))
+            priv->port_type = NM_TYPE_SETTING_INFINIBAND;
         else
-            priv->slave_type = NM_TYPE_SETTING_WIRED;
+            priv->port_type = NM_TYPE_SETTING_WIRED;
     }
 }
 
@@ -68,11 +68,11 @@ team_connection_type_filter(GType connection_type, gpointer user_data)
     NmtPageTeam        *team = user_data;
     NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(team);
 
-    if (priv->slave_type != NM_TYPE_SETTING_WIRED) {
+    if (priv->port_type != NM_TYPE_SETTING_WIRED) {
         if (connection_type == NM_TYPE_SETTING_INFINIBAND)
             return TRUE;
     }
-    if (priv->slave_type != NM_TYPE_SETTING_INFINIBAND) {
+    if (priv->port_type != NM_TYPE_SETTING_INFINIBAND) {
         if (connection_type == NM_TYPE_SETTING_WIRED || connection_type == NM_TYPE_SETTING_WIRELESS
             || connection_type == NM_TYPE_SETTING_VLAN)
             return TRUE;
@@ -126,12 +126,12 @@ nmt_page_team_constructed(GObject *object)
     widget = nmt_newt_label_new(_("Slaves"));
     nmt_newt_grid_add(grid, widget, 0, 0);
 
-    widget = nmt_slave_list_new(conn, team_connection_type_filter, team);
-    g_signal_connect(widget, "notify::connections", G_CALLBACK(slaves_changed), team);
+    widget = nmt_port_list_new(conn, team_connection_type_filter, team);
+    g_signal_connect(widget, "notify::connections", G_CALLBACK(ports_changed), team);
     nmt_newt_grid_add(grid, widget, 0, 1);
     nmt_newt_widget_set_padding(widget, 0, 0, 0, 1);
-    priv->slaves = NMT_SLAVE_LIST(widget);
-    slaves_changed(G_OBJECT(priv->slaves), NULL, team);
+    priv->ports = NMT_PORT_LIST(widget);
+    ports_changed(G_OBJECT(priv->ports), NULL, team);
 
     widget = nmt_newt_label_new(_("JSON configuration"));
     nmt_newt_grid_add(grid, widget, 0, 2);
@@ -156,7 +156,7 @@ nmt_page_team_saved(NmtEditorPage *editor_page)
 {
     NmtPageTeamPrivate *priv = NMT_PAGE_TEAM_GET_PRIVATE(editor_page);
 
-    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->slaves));
+    nmt_edit_connection_list_recommit(NMT_EDIT_CONNECTION_LIST(priv->ports));
 }
 
 static void
diff --git a/src/nmtui/nmt-port-list.c b/src/nmtui/nmt-port-list.c
new file mode 100644
index 00000000..9f964c73
--- /dev/null
+++ b/src/nmtui/nmt-port-list.c
@@ -0,0 +1,254 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+/**
+ * SECTION:nmt-port-list:
+ * @short_description: An editable list of a connection's ports
+ *
+ * #NmtPortList implements an #NmtEditConnectionList for the
+ * ports of a connection.
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "nmt-port-list.h"
+
+G_DEFINE_TYPE(NmtPortList, nmt_port_list, NMT_TYPE_EDIT_CONNECTION_LIST)
+
+#define NMT_PORT_LIST_GET_PRIVATE(o) \
+    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_PORT_LIST, NmtPortListPrivate))
+
+typedef struct {
+    NMConnection *controller;
+    const char   *controller_type, *controller_uuid;
+
+    NmtAddConnectionTypeFilter type_filter;
+    gpointer                   type_filter_data;
+} NmtPortListPrivate;
+
+enum {
+    PROP_0,
+    PROP_CONTROLLER,
+    PROP_TYPE_FILTER,
+    PROP_TYPE_FILTER_DATA,
+
+    LAST_PROP
+};
+
+static gboolean nmt_port_list_connection_filter(NmtEditConnectionList *list,
+                                                NMConnection          *connection,
+                                                gpointer               user_data);
+
+/**
+ * nmt_port_list_new:
+ * @controller: the controller #NMConnection whose ports are being listed
+ * @type_filter: (nullable): a function to limit the available port types
+ * @type_filter_data: (nullable): data for @type_filter.
+ *
+ * Creates a new #NmtPortList.
+ *
+ * If @type_filter is non-%NULL, it will be used to limit the connection
+ * types that are available when the user clicks on the "Add" button to add
+ * a new port. If the @type_filter filters the list down to only a single
+ * connection type, then the user will not be presented with a connection-type
+ * dialog, and will instead be immediately taken to an editor window for the
+ * new port after clicking "Add".
+ *
+ * Returns: a new #NmtPortList.
+ */
+NmtNewtWidget *
+nmt_port_list_new(NMConnection              *controller,
+                  NmtAddConnectionTypeFilter type_filter,
+                  gpointer                   type_filter_data)
+{
+    return g_object_new(NMT_TYPE_PORT_LIST,
+                        "controller",
+                        controller,
+                        "type-filter",
+                        type_filter,
+                        "type-filter-data",
+                        type_filter_data,
+                        "grouped",
+                        FALSE,
+                        "connection-filter",
+                        nmt_port_list_connection_filter,
+                        NULL);
+}
+
+static void
+nmt_port_list_init(NmtPortList *list)
+{}
+
+static void
+nmt_port_list_finalize(GObject *object)
+{
+    NmtPortListPrivate *priv = NMT_PORT_LIST_GET_PRIVATE(object);
+
+    g_object_unref(priv->controller);
+
+    G_OBJECT_CLASS(nmt_port_list_parent_class)->finalize(object);
+}
+
+static gboolean
+nmt_port_list_connection_filter(NmtEditConnectionList *list,
+                                NMConnection          *connection,
+                                gpointer               user_data)
+{
+    NmtPortListPrivate  *priv = NMT_PORT_LIST_GET_PRIVATE(list);
+    NMSettingConnection *s_con;
+    const char          *controller, *controller_ifname, *port_type;
+
+    s_con = nm_connection_get_setting_connection(connection);
+    g_return_val_if_fail(s_con != NULL, FALSE);
+
+    port_type = nm_setting_connection_get_slave_type(s_con);
+    if (g_strcmp0(port_type, priv->controller_type) != 0)
+        return FALSE;
+
+    controller = nm_setting_connection_get_master(s_con);
+    if (!controller)
+        return FALSE;
+
+    controller_ifname = nm_connection_get_interface_name(priv->controller);
+    if (g_strcmp0(controller, controller_ifname) != 0
+        && g_strcmp0(controller, priv->controller_uuid) != 0)
+        return FALSE;
+
+    return TRUE;
+}
+
+static void
+nmt_port_list_add_connection(NmtEditConnectionList *list)
+{
+    NmtPortListPrivate *priv = NMT_PORT_LIST_GET_PRIVATE(list);
+
+    nmt_add_connection_full(_("Select the type of slave connection you wish to add."),
+                            NULL,
+                            priv->controller,
+                            priv->type_filter,
+                            priv->type_filter_data);
+}
+
+static void
+nmt_port_list_edit_connection(NmtEditConnectionList *list, NMConnection *connection)
+{
+    nmt_edit_connection(connection);
+}
+
+static void
+nmt_port_list_remove_connection(NmtEditConnectionList *list, NMRemoteConnection *connection)
+{
+    nmt_remove_connection(connection);
+}
+
+static void
+nmt_port_list_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NmtPortListPrivate *priv = NMT_PORT_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_CONTROLLER:
+        priv->controller = g_value_dup_object(value);
+        if (priv->controller) {
+            NMSettingConnection *s_con = nm_connection_get_setting_connection(priv->controller);
+
+            priv->controller_type = nm_setting_connection_get_connection_type(s_con);
+            priv->controller_uuid = nm_setting_connection_get_uuid(s_con);
+        }
+        break;
+    case PROP_TYPE_FILTER:
+        priv->type_filter = g_value_get_pointer(value);
+        break;
+    case PROP_TYPE_FILTER_DATA:
+        priv->type_filter_data = g_value_get_pointer(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_port_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NmtPortListPrivate *priv = NMT_PORT_LIST_GET_PRIVATE(object);
+
+    switch (prop_id) {
+    case PROP_CONTROLLER:
+        g_value_set_object(value, priv->controller);
+        break;
+    case PROP_TYPE_FILTER:
+        g_value_set_pointer(value, priv->type_filter);
+        break;
+    case PROP_TYPE_FILTER_DATA:
+        g_value_set_pointer(value, priv->type_filter_data);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+nmt_port_list_class_init(NmtPortListClass *list_class)
+{
+    GObjectClass               *object_class          = G_OBJECT_CLASS(list_class);
+    NmtEditConnectionListClass *connection_list_class = NMT_EDIT_CONNECTION_LIST_CLASS(list_class);
+
+    g_type_class_add_private(list_class, sizeof(NmtPortListPrivate));
+
+    /* virtual methods */
+    object_class->set_property = nmt_port_list_set_property;
+    object_class->get_property = nmt_port_list_get_property;
+    object_class->finalize     = nmt_port_list_finalize;
+
+    connection_list_class->add_connection    = nmt_port_list_add_connection;
+    connection_list_class->edit_connection   = nmt_port_list_edit_connection;
+    connection_list_class->remove_connection = nmt_port_list_remove_connection;
+
+    /**
+     * NmtPortList:controller:
+     *
+     * The controller #NMConnection whose ports are being displayed.
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_CONTROLLER,
+        g_param_spec_object("controller",
+                            "",
+                            "",
+                            NM_TYPE_CONNECTION,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtPortList:type-filter:
+     *
+     * If non-%NULL, this will be used to limit the connection types
+     * that are available when the user clicks on the "Add" button to
+     * add a new port. If the filter filters the list down to only a
+     * single connection type, then the user will not be presented
+     * with a connection-type dialog, and will instead be immediately
+     * taken to an editor window for the new port after clicking
+     * "Add".
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TYPE_FILTER,
+        g_param_spec_pointer("type-filter",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+    /**
+     * NmtPortList:type-filter-data:
+     *
+     * User data passed to #NmtPortList:type-filter
+     */
+    g_object_class_install_property(
+        object_class,
+        PROP_TYPE_FILTER_DATA,
+        g_param_spec_pointer("type-filter-data",
+                             "",
+                             "",
+                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+}
diff --git a/src/nmtui/nmt-port-list.h b/src/nmtui/nmt-port-list.h
new file mode 100644
index 00000000..df4f22ff
--- /dev/null
+++ b/src/nmtui/nmt-port-list.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ */
+
+#ifndef NMT_PORT_LIST_H
+#define NMT_PORT_LIST_H
+
+#include "nmt-edit-connection-list.h"
+#include "nmtui-edit.h"
+
+#define NMT_TYPE_PORT_LIST (nmt_port_list_get_type())
+#define NMT_PORT_LIST(obj) (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_PORT_LIST, NmtPortList))
+#define NMT_PORT_LIST_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_PORT_LIST, NmtPortListClass))
+#define NMT_IS_PORT_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_PORT_LIST))
+#define NMT_IS_PORT_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_PORT_LIST))
+#define NMT_PORT_LIST_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_PORT_LIST, NmtPortListClass))
+
+typedef struct {
+    NmtEditConnectionList parent;
+
+} NmtPortList;
+
+typedef struct {
+    NmtEditConnectionListClass parent;
+
+} NmtPortListClass;
+
+GType nmt_port_list_get_type(void);
+
+NmtNewtWidget *nmt_port_list_new(NMConnection              *controller,
+                                 NmtAddConnectionTypeFilter type_filter,
+                                 gpointer                   type_filter_data);
+
+#endif /* NMT_PORT_LIST_H */
diff --git a/src/nmtui/nmt-slave-list.c b/src/nmtui/nmt-slave-list.c
deleted file mode 100644
index b3ba8dc4..00000000
--- a/src/nmtui/nmt-slave-list.c
+++ /dev/null
@@ -1,253 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2013 Red Hat, Inc.
- */
-
-/**
- * SECTION:nmt-slave-list:
- * @short_description: An editable list of a connection's slaves
- *
- * #NmtSlaveList implements an #NmtEditConnectionList for the
- * slaves of a connection.
- */
-
-#include "libnm-client-aux-extern/nm-default-client.h"
-
-#include "nmt-slave-list.h"
-
-G_DEFINE_TYPE(NmtSlaveList, nmt_slave_list, NMT_TYPE_EDIT_CONNECTION_LIST)
-
-#define NMT_SLAVE_LIST_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NMT_TYPE_SLAVE_LIST, NmtSlaveListPrivate))
-
-typedef struct {
-    NMConnection *master;
-    const char   *master_type, *master_uuid;
-
-    NmtAddConnectionTypeFilter type_filter;
-    gpointer                   type_filter_data;
-} NmtSlaveListPrivate;
-
-enum {
-    PROP_0,
-    PROP_MASTER,
-    PROP_TYPE_FILTER,
-    PROP_TYPE_FILTER_DATA,
-
-    LAST_PROP
-};
-
-static gboolean nmt_slave_list_connection_filter(NmtEditConnectionList *list,
-                                                 NMConnection          *connection,
-                                                 gpointer               user_data);
-
-/**
- * nmt_slave_list_new:
- * @master: the master #NMConnection whose slaves are being listed
- * @type_filter: (allow-none): a function to limit the available slave types
- * @type_filter_data: (allow-none): data for @type_filter.
- *
- * Creates a new #NmtSlaveList.
- *
- * If @type_filter is non-%NULL, it will be used to limit the connection
- * types that are available when the user clicks on the "Add" button to add
- * a new slave. If the @type_filter filters the list down to only a single
- * connection type, then the user will not be presented with a connection-type
- * dialog, and will instead be immediately taken to an editor window for the
- * new slave after clicking "Add".
- *
- * Returns: a new #NmtSlaveList.
- */
-NmtNewtWidget *
-nmt_slave_list_new(NMConnection              *master,
-                   NmtAddConnectionTypeFilter type_filter,
-                   gpointer                   type_filter_data)
-{
-    return g_object_new(NMT_TYPE_SLAVE_LIST,
-                        "master",
-                        master,
-                        "type-filter",
-                        type_filter,
-                        "type-filter-data",
-                        type_filter_data,
-                        "grouped",
-                        FALSE,
-                        "connection-filter",
-                        nmt_slave_list_connection_filter,
-                        NULL);
-}
-
-static void
-nmt_slave_list_init(NmtSlaveList *list)
-{}
-
-static void
-nmt_slave_list_finalize(GObject *object)
-{
-    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(object);
-
-    g_object_unref(priv->master);
-
-    G_OBJECT_CLASS(nmt_slave_list_parent_class)->finalize(object);
-}
-
-static gboolean
-nmt_slave_list_connection_filter(NmtEditConnectionList *list,
-                                 NMConnection          *connection,
-                                 gpointer               user_data)
-{
-    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(list);
-    NMSettingConnection *s_con;
-    const char          *master, *master_ifname, *slave_type;
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_return_val_if_fail(s_con != NULL, FALSE);
-
-    slave_type = nm_setting_connection_get_slave_type(s_con);
-    if (g_strcmp0(slave_type, priv->master_type) != 0)
-        return FALSE;
-
-    master = nm_setting_connection_get_master(s_con);
-    if (!master)
-        return FALSE;
-
-    master_ifname = nm_connection_get_interface_name(priv->master);
-    if (g_strcmp0(master, master_ifname) != 0 && g_strcmp0(master, priv->master_uuid) != 0)
-        return FALSE;
-
-    return TRUE;
-}
-
-static void
-nmt_slave_list_add_connection(NmtEditConnectionList *list)
-{
-    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(list);
-
-    nmt_add_connection_full(_("Select the type of slave connection you wish to add."),
-                            NULL,
-                            priv->master,
-                            priv->type_filter,
-                            priv->type_filter_data);
-}
-
-static void
-nmt_slave_list_edit_connection(NmtEditConnectionList *list, NMConnection *connection)
-{
-    nmt_edit_connection(connection);
-}
-
-static void
-nmt_slave_list_remove_connection(NmtEditConnectionList *list, NMRemoteConnection *connection)
-{
-    nmt_remove_connection(connection);
-}
-
-static void
-nmt_slave_list_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
-{
-    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(object);
-
-    switch (prop_id) {
-    case PROP_MASTER:
-        priv->master = g_value_dup_object(value);
-        if (priv->master) {
-            NMSettingConnection *s_con = nm_connection_get_setting_connection(priv->master);
-
-            priv->master_type = nm_setting_connection_get_connection_type(s_con);
-            priv->master_uuid = nm_setting_connection_get_uuid(s_con);
-        }
-        break;
-    case PROP_TYPE_FILTER:
-        priv->type_filter = g_value_get_pointer(value);
-        break;
-    case PROP_TYPE_FILTER_DATA:
-        priv->type_filter_data = g_value_get_pointer(value);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void
-nmt_slave_list_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
-{
-    NmtSlaveListPrivate *priv = NMT_SLAVE_LIST_GET_PRIVATE(object);
-
-    switch (prop_id) {
-    case PROP_MASTER:
-        g_value_set_object(value, priv->master);
-        break;
-    case PROP_TYPE_FILTER:
-        g_value_set_pointer(value, priv->type_filter);
-        break;
-    case PROP_TYPE_FILTER_DATA:
-        g_value_set_pointer(value, priv->type_filter_data);
-        break;
-    default:
-        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
-        break;
-    }
-}
-
-static void
-nmt_slave_list_class_init(NmtSlaveListClass *list_class)
-{
-    GObjectClass               *object_class          = G_OBJECT_CLASS(list_class);
-    NmtEditConnectionListClass *connection_list_class = NMT_EDIT_CONNECTION_LIST_CLASS(list_class);
-
-    g_type_class_add_private(list_class, sizeof(NmtSlaveListPrivate));
-
-    /* virtual methods */
-    object_class->set_property = nmt_slave_list_set_property;
-    object_class->get_property = nmt_slave_list_get_property;
-    object_class->finalize     = nmt_slave_list_finalize;
-
-    connection_list_class->add_connection    = nmt_slave_list_add_connection;
-    connection_list_class->edit_connection   = nmt_slave_list_edit_connection;
-    connection_list_class->remove_connection = nmt_slave_list_remove_connection;
-
-    /**
-     * NmtSlaveList:master:
-     *
-     * The master #NMConnection whose slaves are being displayed.
-     */
-    g_object_class_install_property(
-        object_class,
-        PROP_MASTER,
-        g_param_spec_object("master",
-                            "",
-                            "",
-                            NM_TYPE_CONNECTION,
-                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-    /**
-     * NmtSlaveList:type-filter:
-     *
-     * If non-%NULL, this will be used to limit the connection types
-     * that are available when the user clicks on the "Add" button to
-     * add a new slave. If the filter filters the list down to only a
-     * single connection type, then the user will not be presented
-     * with a connection-type dialog, and will instead be immediately
-     * taken to an editor window for the new slave after clicking
-     * "Add".
-     */
-    g_object_class_install_property(
-        object_class,
-        PROP_TYPE_FILTER,
-        g_param_spec_pointer("type-filter",
-                             "",
-                             "",
-                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-    /**
-     * NmtSlaveList:type-filter-data:
-     *
-     * User data passed to #NmtSlaveList:type-filter
-     */
-    g_object_class_install_property(
-        object_class,
-        PROP_TYPE_FILTER_DATA,
-        g_param_spec_pointer("type-filter-data",
-                             "",
-                             "",
-                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
-}
diff --git a/src/nmtui/nmt-slave-list.h b/src/nmtui/nmt-slave-list.h
deleted file mode 100644
index 7bb60c59..00000000
--- a/src/nmtui/nmt-slave-list.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2013 Red Hat, Inc.
- */
-
-#ifndef NMT_SLAVE_LIST_H
-#define NMT_SLAVE_LIST_H
-
-#include "nmt-edit-connection-list.h"
-#include "nmtui-edit.h"
-
-#define NMT_TYPE_SLAVE_LIST (nmt_slave_list_get_type())
-#define NMT_SLAVE_LIST(obj) \
-    (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NMT_TYPE_SLAVE_LIST, NmtSlaveList))
-#define NMT_SLAVE_LIST_CLASS(klass) \
-    (G_TYPE_CHECK_CLASS_CAST((klass), NMT_TYPE_SLAVE_LIST, NmtSlaveListClass))
-#define NMT_IS_SLAVE_LIST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMT_TYPE_SLAVE_LIST))
-#define NMT_IS_SLAVE_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMT_TYPE_SLAVE_LIST))
-#define NMT_SLAVE_LIST_GET_CLASS(obj) \
-    (G_TYPE_INSTANCE_GET_CLASS((obj), NMT_TYPE_SLAVE_LIST, NmtSlaveListClass))
-
-typedef struct {
-    NmtEditConnectionList parent;
-
-} NmtSlaveList;
-
-typedef struct {
-    NmtEditConnectionListClass parent;
-
-} NmtSlaveListClass;
-
-GType nmt_slave_list_get_type(void);
-
-NmtNewtWidget *nmt_slave_list_new(NMConnection              *master,
-                                  NmtAddConnectionTypeFilter type_filter,
-                                  gpointer                   type_filter_data);
-
-#endif /* NMT_SLAVE_LIST_H */
diff --git a/src/nmtui/nmt-utils.c b/src/nmtui/nmt-utils.c
index 84d9e9bb..7a500542 100644
--- a/src/nmtui/nmt-utils.c
+++ b/src/nmtui/nmt-utils.c
@@ -78,7 +78,7 @@ nmt_sync_op_wait_boolean(NmtSyncOp *op, GError **error)
  * nmt_sync_op_complete_boolean:
  * @op: the #NmtSyncOp
  * @result: the result of the operation
- * @error: (allow-none): the error, or %NULL
+ * @error: (nullable): the error, or %NULL
  *
  * Completes @op and returns @result and/or @error to the caller.
  */
@@ -115,7 +115,7 @@ nmt_sync_op_wait_pointer(NmtSyncOp *op, GError **error)
  * nmt_sync_op_complete_pointer:
  * @op: the #NmtSyncOp
  * @result: the result of the operation
- * @error: (allow-none): the error, or %NULL
+ * @error: (nullable): the error, or %NULL
  *
  * Completes @op and returns @result and/or @error to the caller.
  */
diff --git a/src/nmtui/nmt-widget-list.c b/src/nmtui/nmt-widget-list.c
index 18d4d16e..d150eee0 100644
--- a/src/nmtui/nmt-widget-list.c
+++ b/src/nmtui/nmt-widget-list.c
@@ -95,7 +95,7 @@ static void remove_clicked(NmtNewtButton *button, gpointer user_data);
  * @create_callback: callback to create new widgets
  * @user_data: user data for @create_callback
  * @destroy_notify: #GDestroyNotify for @user_data
- * @empty_widget: (allow-none): a widget to display when there are
+ * @empty_widget: (nullable): a widget to display when there are
  *   no "real" widgets in the list.
  *
  * Creates a new #NmtWidgetList.
diff --git a/src/nmtui/nmtui-connect.c b/src/nmtui/nmtui-connect.c
index 8c4625ec..0dfbf6ac 100644
--- a/src/nmtui/nmtui-connect.c
+++ b/src/nmtui/nmtui-connect.c
@@ -26,58 +26,6 @@
 #include "libnmc-base/nm-client-utils.h"
 #include "nmt-utils.h"
 
-/**
- * Runs openconnect to authenticate. The current screen state is saved
- * before starting the command and restored after it returns.
- */
-static gboolean
-openconnect_authenticate(NMConnection *connection, char **cookie, char **gateway, char **gwcert)
-{
-    GError       *error = NULL;
-    NMSettingVpn *s_vpn;
-    gboolean      ret;
-    int           status = 0;
-    const char   *gw, *port;
-
-    nmt_newt_message_dialog(
-        _("openconnect will be run to authenticate.\nIt will return to nmtui when completed."));
-
-    /* Get port */
-    s_vpn = nm_connection_get_setting_vpn(connection);
-    gw    = nm_setting_vpn_get_data_item(s_vpn, "gateway");
-    port  = gw ? strrchr(gw, ':') : NULL;
-
-    newtSuspend();
-
-    ret = nm_vpn_openconnect_authenticate_helper(gw, cookie, gateway, gwcert, &status, &error);
-
-    newtResume();
-
-    if (!ret) {
-        nmt_newt_message_dialog(_("Error: openconnect failed: %s"), error->message);
-        g_clear_error(&error);
-        return FALSE;
-    }
-
-    if (WIFEXITED(status)) {
-        if (WEXITSTATUS(status) != 0) {
-            nmt_newt_message_dialog(_("openconnect failed with status %d"), WEXITSTATUS(status));
-            return FALSE;
-        }
-    } else if (WIFSIGNALED(status)) {
-        nmt_newt_message_dialog(_("openconnect failed with signal %d"), WTERMSIG(status));
-        return FALSE;
-    }
-
-    if (gateway && *gateway && port) {
-        char *tmp = *gateway;
-        *gateway  = g_strdup_printf("%s%s", *gateway, port);
-        g_free(tmp);
-    }
-
-    return TRUE;
-}
-
 static void
 secrets_requested(NMSecretAgentSimple *agent,
                   const char          *request_id,
@@ -86,9 +34,8 @@ secrets_requested(NMSecretAgentSimple *agent,
                   GPtrArray           *secrets,
                   gpointer             user_data)
 {
-    NmtNewtForm  *form;
     NMConnection *connection = NM_CONNECTION(user_data);
-    int           i;
+    gboolean      success    = FALSE;
 
     /* Get secrets for OpenConnect VPN */
     if (connection && nm_connection_is_type(connection, NM_SETTING_VPN_SETTING_NAME)) {
@@ -96,45 +43,32 @@ secrets_requested(NMSecretAgentSimple *agent,
 
         if (nm_streq0(nm_setting_vpn_get_service_type(s_vpn),
                       NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT)) {
-            gs_free char *cookie  = NULL;
-            gs_free char *gateway = NULL;
-            gs_free char *gwcert  = NULL;
-
-            openconnect_authenticate(connection, &cookie, &gateway, &gwcert);
-
-            for (i = 0; i < secrets->len; i++) {
-                NMSecretAgentSimpleSecret *secret = secrets->pdata[i];
-
-                if (secret->secret_type != NM_SECRET_AGENT_SECRET_TYPE_VPN_SECRET)
-                    continue;
-                if (!nm_streq0(secret->vpn_type, NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT))
-                    continue;
-                if (nm_streq0(secret->entry_id,
-                              NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "cookie")) {
-                    g_free(secret->value);
-                    secret->value = g_steal_pointer(&cookie);
-                } else if (nm_streq0(secret->entry_id,
-                                     NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gateway")) {
-                    g_free(secret->value);
-                    secret->value = g_steal_pointer(&gateway);
-                } else if (nm_streq0(secret->entry_id,
-                                     NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gwcert")) {
-                    g_free(secret->value);
-                    secret->value = g_steal_pointer(&gwcert);
-                }
-            }
+            gs_free_error GError *error = NULL;
+
+            nmt_newt_message_dialog(_("openconnect will be run to authenticate.\nIt will return to "
+                                      "nmtui when completed."));
+
+            newtSuspend();
+
+            success = nm_vpn_openconnect_authenticate_helper(s_vpn, secrets, &error);
+
+            newtResume();
+
+            if (!success)
+                nmt_newt_message_dialog(_("Error: openconnect failed: %s"), error->message);
         }
     }
 
-    form = nmt_password_dialog_new(request_id, title, msg, secrets);
-    nmt_newt_form_run_sync(form);
+    if (!success) {
+        gs_unref_object NmtNewtForm *form = NULL;
 
-    if (nmt_password_dialog_succeeded(NMT_PASSWORD_DIALOG(form)))
-        nm_secret_agent_simple_response(agent, request_id, secrets);
-    else
-        nm_secret_agent_simple_response(agent, request_id, NULL);
+        form = nmt_password_dialog_new(request_id, title, msg, secrets);
+        nmt_newt_form_run_sync(form);
 
-    g_object_unref(form);
+        success = nmt_password_dialog_succeeded(NMT_PASSWORD_DIALOG(form));
+    }
+
+    nm_secret_agent_simple_response(agent, request_id, success ? secrets : NULL);
 }
 
 typedef struct {
diff --git a/src/nmtui/nmtui-edit.c b/src/nmtui/nmtui-edit.c
index 4eed22dd..3cedf197 100644
--- a/src/nmtui/nmtui-edit.c
+++ b/src/nmtui/nmtui-edit.c
@@ -49,22 +49,22 @@ edit_connection_list_filter(NmtEditConnectionList *list,
                             gpointer               user_data)
 {
     NMSettingConnection *s_con;
-    const char          *master, *slave_type;
+    const char          *controller, *port_type;
     const char          *uuid, *ifname;
     const GPtrArray     *conns;
     int                  i;
-    gboolean             found_master = FALSE;
+    gboolean             found_controller = FALSE;
 
     s_con = nm_connection_get_setting_connection(connection);
     g_return_val_if_fail(s_con != NULL, FALSE);
 
-    master = nm_setting_connection_get_master(s_con);
-    if (!master)
+    controller = nm_setting_connection_get_master(s_con);
+    if (!controller)
         return TRUE;
-    slave_type = nm_setting_connection_get_slave_type(s_con);
-    if (g_strcmp0(slave_type, NM_SETTING_BOND_SETTING_NAME) != 0
-        && g_strcmp0(slave_type, NM_SETTING_TEAM_SETTING_NAME) != 0
-        && g_strcmp0(slave_type, NM_SETTING_BRIDGE_SETTING_NAME) != 0)
+    port_type = nm_setting_connection_get_slave_type(s_con);
+    if (g_strcmp0(port_type, NM_SETTING_BOND_SETTING_NAME) != 0
+        && g_strcmp0(port_type, NM_SETTING_TEAM_SETTING_NAME) != 0
+        && g_strcmp0(port_type, NM_SETTING_BRIDGE_SETTING_NAME) != 0)
         return TRUE;
 
     conns = nm_client_get_connections(nm_client);
@@ -73,13 +73,13 @@ edit_connection_list_filter(NmtEditConnectionList *list,
 
         uuid   = nm_connection_get_uuid(candidate);
         ifname = nm_connection_get_interface_name(candidate);
-        if (!g_strcmp0(master, uuid) || !g_strcmp0(master, ifname)) {
-            found_master = TRUE;
+        if (!g_strcmp0(controller, uuid) || !g_strcmp0(controller, ifname)) {
+            found_controller = TRUE;
             break;
         }
     }
 
-    return !found_master;
+    return !found_controller;
 }
 
 static NmtNewtForm *
@@ -132,7 +132,7 @@ typedef struct {
 
     char                      *primary_text;
     char                      *secondary_text;
-    NMConnection              *master;
+    NMConnection              *controller;
     NmtAddConnectionTypeFilter type_filter;
     gpointer                   type_filter_data;
 
@@ -144,7 +144,7 @@ enum {
 
     PROP_PRIMARY_TEXT,
     PROP_SECONDARY_TEXT,
-    PROP_MASTER,
+    PROP_CONTROLLER,
     PROP_TYPE_FILTER,
     PROP_TYPE_FILTER_DATA,
 
@@ -158,7 +158,7 @@ create_connection(NmtNewtWidget *widget, gpointer list)
     GType         type = (GType) GPOINTER_TO_SIZE(nmt_newt_listbox_get_active_key(priv->listbox));
     NMConnection *connection;
 
-    connection = nm_editor_utils_create_connection(type, priv->master, nm_client);
+    connection = nm_editor_utils_create_connection(type, priv->controller, nm_client);
     nmt_edit_connection(connection);
     g_object_unref(connection);
 }
@@ -262,7 +262,7 @@ nmt_add_connection_finalize(GObject *object)
 
     g_free(priv->primary_text);
     g_free(priv->secondary_text);
-    g_clear_object(&priv->master);
+    g_clear_object(&priv->controller);
 
     G_OBJECT_CLASS(nmt_add_connection_parent_class)->finalize(object);
 }
@@ -282,8 +282,8 @@ nmt_add_connection_set_property(GObject      *object,
     case PROP_SECONDARY_TEXT:
         priv->secondary_text = g_value_dup_string(value);
         break;
-    case PROP_MASTER:
-        priv->master = g_value_dup_object(value);
+    case PROP_CONTROLLER:
+        priv->controller = g_value_dup_object(value);
         break;
     case PROP_TYPE_FILTER:
         priv->type_filter = g_value_get_pointer(value);
@@ -309,8 +309,8 @@ nmt_add_connection_get_property(GObject *object, guint prop_id, GValue *value, G
     case PROP_SECONDARY_TEXT:
         g_value_set_string(value, priv->secondary_text);
         break;
-    case PROP_MASTER:
-        g_value_set_object(value, priv->master);
+    case PROP_CONTROLLER:
+        g_value_set_object(value, priv->controller);
         break;
     case PROP_TYPE_FILTER:
         g_value_set_pointer(value, priv->type_filter);
@@ -364,8 +364,8 @@ nmt_add_connection_class_init(NmtAddConnectionClass *add_class)
                             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
     g_object_class_install_property(
         object_class,
-        PROP_MASTER,
-        g_param_spec_object("master",
+        PROP_CONTROLLER,
+        g_param_spec_object("controller",
                             "",
                             "",
                             NM_TYPE_CONNECTION,
@@ -399,7 +399,7 @@ nmt_add_connection(void)
 void
 nmt_add_connection_full(const char                *primary_text,
                         const char                *secondary_text,
-                        NMConnection              *master,
+                        NMConnection              *controller,
                         NmtAddConnectionTypeFilter type_filter,
                         gpointer                   type_filter_data)
 {
@@ -412,8 +412,8 @@ nmt_add_connection_full(const char                *primary_text,
                         primary_text,
                         "secondary-text",
                         secondary_text,
-                        "master",
-                        master,
+                        "controller",
+                        controller,
                         "type-filter",
                         type_filter,
                         "type-filter-data",
@@ -500,11 +500,11 @@ void
 nmt_remove_connection(NMRemoteConnection *connection)
 {
     const GPtrArray     *all_conns;
-    GSList              *slaves, *iter;
+    GSList              *ports, *iter;
     int                  i;
-    NMRemoteConnection  *slave;
+    NMRemoteConnection  *port;
     NMSettingConnection *s_con;
-    const char          *uuid, *iface, *master;
+    const char          *uuid, *iface, *controller;
     int                  choice;
 
     choice = nmt_newt_choice_dialog(_("Cancel"),
@@ -521,20 +521,20 @@ nmt_remove_connection(NMRemoteConnection *connection)
     iface = nm_connection_get_interface_name(NM_CONNECTION(connection));
 
     all_conns = nm_client_get_connections(nm_client);
-    slaves    = NULL;
+    ports     = NULL;
     for (i = 0; i < all_conns->len; i++) {
-        slave  = all_conns->pdata[i];
-        s_con  = nm_connection_get_setting_connection(NM_CONNECTION(slave));
-        master = nm_setting_connection_get_master(s_con);
-        if (master) {
-            if (!g_strcmp0(master, uuid) || !g_strcmp0(master, iface))
-                slaves = g_slist_prepend(slaves, g_object_ref(slave));
+        port       = all_conns->pdata[i];
+        s_con      = nm_connection_get_setting_connection(NM_CONNECTION(port));
+        controller = nm_setting_connection_get_master(s_con);
+        if (controller) {
+            if (!g_strcmp0(controller, uuid) || !g_strcmp0(controller, iface))
+                ports = g_slist_prepend(ports, g_object_ref(port));
         }
     }
 
-    for (iter = slaves; iter; iter = iter->next)
+    for (iter = ports; iter; iter = iter->next)
         remove_one_connection(iter->data);
-    g_slist_free_full(slaves, g_object_unref);
+    g_slist_free_full(ports, g_object_unref);
 
     g_object_unref(connection);
 }
diff --git a/src/nmtui/nmtui-edit.h b/src/nmtui/nmtui-edit.h
index e8685602..45d6d7af 100644
--- a/src/nmtui/nmtui-edit.h
+++ b/src/nmtui/nmtui-edit.h
@@ -15,7 +15,7 @@ NmtNewtForm *nmtui_edit(gboolean is_top, int argc, char **argv);
 void nmt_add_connection(void);
 void nmt_add_connection_full(const char                *primary_text,
                              const char                *secondary_text,
-                             NMConnection              *master,
+                             NMConnection              *controller,
                              NmtAddConnectionTypeFilter type_filter,
                              gpointer                   type_filter_data);
 
diff --git a/src/nmtui/nmtui-radio.c b/src/nmtui/nmtui-radio.c
new file mode 100644
index 00000000..c8636037
--- /dev/null
+++ b/src/nmtui/nmtui-radio.c
@@ -0,0 +1,130 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Javier Sánchez Parra, javsanpar@riseup.net
+ */
+
+/**
+ * SECTION:nmtui-radio
+ * @short_description: radio-setting functionality
+ *
+ */
+
+#include "libnm-client-aux-extern/nm-default-client.h"
+
+#include "libnmt-newt/nmt-newt-toggle-button.h"
+#include "libnmt-newt/nmt-newt-types.h"
+#include "libnmt-newt/nmt-newt.h"
+
+#include "nmtui.h"
+#include "nmtui-radio.h"
+#include "nmt-utils.h"
+
+static void
+nmtui_radio_toggle_wifi(GObject *object, gpointer radio_type)
+{
+    NmtNewtToggleButton *button = NMT_NEWT_TOGGLE_BUTTON(object);
+    gboolean             enable_flag;
+
+    enable_flag = nmt_newt_toggle_button_get_active(button);
+    nm_client_wireless_set_enabled(nm_client, enable_flag);
+}
+
+static void
+nmtui_radio_toggle_wwan(GObject *object, gpointer radio_type)
+{
+    NmtNewtToggleButton *button = NMT_NEWT_TOGGLE_BUTTON(object);
+    gboolean             enable_flag;
+
+    enable_flag = nmt_newt_toggle_button_get_active(button);
+    nm_client_wwan_set_enabled(nm_client, enable_flag);
+}
+
+static void
+nmtui_radio_run_dialog(void)
+{
+    gs_unref_object NmtNewtForm *form = NULL;
+    NmtNewtToggleButton         *toggle_wifi, *toggle_wwan;
+    NmtNewtButtonBox            *bbox;
+    NmtNewtWidget               *widget;
+    NmtNewtGrid                 *grid;
+    gboolean                     enable_flag;
+
+    form = g_object_new(NMT_TYPE_NEWT_FORM, "title", _("Set the radio switches status"), NULL);
+
+    widget = nmt_newt_grid_new();
+    nmt_newt_form_set_content(form, widget);
+    grid = NMT_NEWT_GRID(widget);
+
+    widget = nmt_newt_label_new(_("Wi-Fi"));
+    nmt_newt_grid_add(grid, widget, 0, 0);
+
+    widget = nmt_newt_label_new(_("Hardware:"));
+    nmt_newt_grid_add(grid, widget, 0, 1);
+
+    if (!(nm_client_get_radio_flags(nm_client) & NM_RADIO_FLAG_WLAN_AVAILABLE)) {
+        widget = nmt_newt_label_new(_("Missing"));
+    } else {
+        enable_flag = nm_client_wireless_hardware_get_enabled(nm_client);
+        widget      = nmt_newt_label_new(enable_flag ? _("Enabled") : _("Disabled"));
+    }
+    nmt_newt_grid_add(grid, widget, 1, 1);
+    nmt_newt_widget_set_padding(widget, 1, 0, 0, 0);
+
+    widget = nmt_newt_label_new(_("Software:"));
+    nmt_newt_grid_add(grid, widget, 2, 1);
+    nmt_newt_widget_set_padding(widget, 3, 0, 0, 0);
+
+    widget = nmt_newt_toggle_button_new(_("Enabled"), _("Disabled"));
+    nmt_newt_grid_add(grid, widget, 3, 1);
+    nmt_newt_widget_set_padding(widget, 1, 0, 0, 0);
+    toggle_wifi = NMT_NEWT_TOGGLE_BUTTON(widget);
+    enable_flag = nm_client_wireless_get_enabled(nm_client);
+    nmt_newt_toggle_button_set_active(toggle_wifi, enable_flag);
+    g_signal_connect(widget, "activated", G_CALLBACK(nmtui_radio_toggle_wifi), NULL);
+
+    widget = nmt_newt_label_new(_("WWAN"));
+    nmt_newt_grid_add(grid, widget, 0, 2);
+    nmt_newt_widget_set_padding(widget, 0, 1, 0, 0);
+
+    widget = nmt_newt_label_new(_("Hardware:"));
+    nmt_newt_grid_add(grid, widget, 0, 3);
+
+    if (!(nm_client_get_radio_flags(nm_client) & NM_RADIO_FLAG_WWAN_AVAILABLE)) {
+        widget = nmt_newt_label_new(_("Missing"));
+    } else {
+        enable_flag = nm_client_wwan_hardware_get_enabled(nm_client);
+        widget      = nmt_newt_label_new(enable_flag ? _("Enabled") : _("Disabled"));
+    }
+    nmt_newt_grid_add(grid, widget, 1, 3);
+    nmt_newt_widget_set_padding(widget, 1, 0, 0, 0);
+
+    widget = nmt_newt_label_new(_("Software:"));
+    nmt_newt_grid_add(grid, widget, 2, 3);
+    nmt_newt_widget_set_padding(widget, 3, 0, 0, 0);
+
+    widget = nmt_newt_toggle_button_new(_("Enabled"), _("Disabled"));
+    nmt_newt_grid_add(grid, widget, 3, 3);
+    nmt_newt_widget_set_padding(widget, 1, 0, 0, 0);
+    toggle_wwan = NMT_NEWT_TOGGLE_BUTTON(widget);
+    enable_flag = nm_client_wwan_get_enabled(nm_client);
+    nmt_newt_toggle_button_set_active(toggle_wwan, enable_flag);
+    g_signal_connect(widget, "activated", G_CALLBACK(nmtui_radio_toggle_wwan), NULL);
+
+    widget = nmt_newt_button_box_new(NMT_NEWT_BUTTON_BOX_HORIZONTAL);
+    nmt_newt_grid_add(grid, widget, 3, 4);
+    nmt_newt_widget_set_padding(widget, 0, 1, 0, 0);
+    bbox = NMT_NEWT_BUTTON_BOX(widget);
+
+    widget = nmt_newt_button_box_add_end(bbox, _("Back"));
+    nmt_newt_widget_set_exit_on_activate(widget, TRUE);
+
+    nmt_newt_form_run_sync(form);
+}
+
+NmtNewtForm *
+nmtui_radio(gboolean is_top, int argc, char **argv)
+{
+    nmtui_radio_run_dialog();
+
+    return NULL;
+}
diff --git a/src/nmtui/nmtui-radio.h b/src/nmtui/nmtui-radio.h
new file mode 100644
index 00000000..53d60b9a
--- /dev/null
+++ b/src/nmtui/nmtui-radio.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Javier Sánchez Parra, javsanpar@riseup.net
+ */
+
+#ifndef NMTUI_RADIO_H
+#define NMTUI_RADIO_H
+
+NmtNewtForm *nmtui_radio(gboolean is_top, int argc, char **argv);
+
+#endif /* NMTUI_RADIO_H */
diff --git a/src/nmtui/nmtui.c b/src/nmtui/nmtui.c
index 37c26e9d..9927e40a 100644
--- a/src/nmtui/nmtui.c
+++ b/src/nmtui/nmtui.c
@@ -26,6 +26,7 @@
 #include "nmtui-edit.h"
 #include "nmtui-connect.h"
 #include "nmtui-hostname.h"
+#include "nmtui-radio.h"
 
 NMClient         *nm_client;
 static GMainLoop *loop;
@@ -39,7 +40,8 @@ static const struct {
 } subprograms[] = {
     {"edit", "nmtui-edit", N_("connection"), N_("Edit a connection"), nmtui_edit},
     {"connect", "nmtui-connect", N_("connection"), N_("Activate a connection"), nmtui_connect},
-    {"hostname", "nmtui-hostname", N_("new hostname"), N_("Set system hostname"), nmtui_hostname}};
+    {"hostname", "nmtui-hostname", N_("new hostname"), N_("Set system hostname"), nmtui_hostname},
+    {"radio", "nmtui-radio", N_("radio"), N_("Radio"), nmtui_radio}};
 static const int    num_subprograms = G_N_ELEMENTS(subprograms);
 static NmtNewtForm *toplevel_form;
 
diff --git a/src/tests/client/meson.build b/src/tests/client/meson.build
index 6dc0f2a2..8c36e405 100644
--- a/src/tests/client/meson.build
+++ b/src/tests/client/meson.build
@@ -8,9 +8,28 @@ test(
     source_root,
     python.path(),
     '--',
+    'TestNmcli',
   ],
   env: [
     'LIBTOOL=',
   ],
   timeout: 120,
 )
+
+if enable_nm_cloud_setup
+  test(
+    'check-local-tests-cloud-setup',
+    find_program(join_paths(source_root, 'src/tests/client/test-client.sh')),
+    args: [
+      build_root,
+      source_root,
+      python.path(),
+      '--',
+      'TestNmCloudSetup',
+    ],
+    env: [
+      'LIBTOOL=',
+    ],
+    timeout: 120,
+  )
+endif
diff --git a/src/tests/client/test-client.check-on-disk/test_002.expected b/src/tests/client/test-client.check-on-disk/test_002.expected
index 8f7acd52..3bfc3529 100644
--- a/src/tests/client/test-client.check-on-disk/test_002.expected
+++ b/src/tests/client/test-client.check-on-disk/test_002.expected
@@ -502,12 +502,12 @@ NAME   UUID                                  TYPE      DEVICE
 con-1  5fcfd6d7-1e63-3332-8826-a7eda103792d  ethernet  --     
 
 <<<
-size: 1416
+size: 1373
 location: src/tests/client/test-client.py:test_002()/23
 cmd: $NMCLI c s con-1
 lang: C
 returncode: 0
-stdout: 1288 bytes
+stdout: 1245 bytes
 >>>
 connection.id:                          con-1
 connection.uuid:                        5fcfd6d7-1e63-3332-8826-a7eda103792d
@@ -520,7 +520,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -538,12 +537,12 @@ connection.wait-device-timeout:         -1
 connection.wait-activation-delay:       -1
 
 <<<
-size: 1428
+size: 1384
 location: src/tests/client/test-client.py:test_002()/24
 cmd: $NMCLI c s con-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 1290 bytes
+stdout: 1246 bytes
 >>>
 connection.id:                          con-1
 connection.uuid:                        5fcfd6d7-1e63-3332-8826-a7eda103792d
@@ -556,7 +555,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
diff --git a/src/tests/client/test-client.check-on-disk/test_003.expected b/src/tests/client/test-client.check-on-disk/test_003.expected
index e505432c..38add2ca 100644
--- a/src/tests/client/test-client.check-on-disk/test_003.expected
+++ b/src/tests/client/test-client.check-on-disk/test_003.expected
@@ -182,12 +182,12 @@ id
 path
 uuid
 <<<
-size: 5096
+size: 5182
 location: src/tests/client/test-client.py:test_003()/14
 cmd: $NMCLI con s con-gsm1
 lang: C
 returncode: 0
-stdout: 4963 bytes
+stdout: 5049 bytes
 >>>
 connection.id:                          con-gsm1
 connection.uuid:                        UUID-con-gsm1-REPLACED-REPLACED-REPL
@@ -200,7 +200,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -266,6 +265,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -293,18 +293,20 @@ gsm.device-id:                          --
 gsm.sim-id:                             --
 gsm.sim-operator-id:                    --
 gsm.mtu:                                auto
+gsm.initial-eps-bearer-configure:       no
+gsm.initial-eps-bearer-apn:             --
 proxy.method:                           none
 proxy.browser-only:                     no
 proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 5134
+size: 5220
 location: src/tests/client/test-client.py:test_003()/15
 cmd: $NMCLI con s con-gsm1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4991 bytes
+stdout: 5077 bytes
 >>>
 connection.id:                          con-gsm1
 connection.uuid:                        UUID-con-gsm1-REPLACED-REPLACED-REPL
@@ -317,7 +319,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -383,6 +384,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -410,48 +412,50 @@ gsm.device-id:                          --
 gsm.sim-id:                             --
 gsm.sim-operator-id:                    --
 gsm.mtu:                                automatyczne
+gsm.initial-eps-bearer-configure:       nie
+gsm.initial-eps-bearer-apn:             --
 proxy.method:                           none
 proxy.browser-only:                     nie
 proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 505
+size: 507
 location: src/tests/client/test-client.py:test_003()/16
 cmd: $NMCLI -g all con s con-gsm1
 lang: C
 returncode: 0
-stdout: 366 bytes
+stdout: 368 bytes
 >>>
-connection:con-gsm1:UUID-con-gsm1-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-gsm1:UUID-con-gsm1-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::: :0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 serial:5:8:even:1:100
-gsm:no:::<hidden>:0:xyz.con-gsm1::<hidden>:0:no::::auto
+gsm:no:::<hidden>:0:xyz.con-gsm1::<hidden>:0:no::::auto:no:
 proxy:none:no::
 
 <<<
-size: 515
+size: 517
 location: src/tests/client/test-client.py:test_003()/17
 cmd: $NMCLI -g all con s con-gsm1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 366 bytes
+stdout: 368 bytes
 >>>
-connection:con-gsm1:UUID-con-gsm1-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-gsm1:UUID-con-gsm1-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::: :0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 serial:5:8:even:1:100
-gsm:no:::<hidden>:0:xyz.con-gsm1::<hidden>:0:no::::auto
+gsm:no:::<hidden>:0:xyz.con-gsm1::<hidden>:0:no::::auto:no:
 proxy:none:no::
 
 <<<
-size: 5084
+size: 5170
 location: src/tests/client/test-client.py:test_003()/18
 cmd: $NMCLI con s con-gsm2
 lang: C
 returncode: 0
-stdout: 4951 bytes
+stdout: 5037 bytes
 >>>
 connection.id:                          con-gsm2
 connection.uuid:                        UUID-con-gsm2-REPLACED-REPLACED-REPL
@@ -464,7 +468,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -530,6 +533,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -557,18 +561,20 @@ gsm.device-id:                          --
 gsm.sim-id:                             --
 gsm.sim-operator-id:                    --
 gsm.mtu:                                auto
+gsm.initial-eps-bearer-configure:       no
+gsm.initial-eps-bearer-apn:             --
 proxy.method:                           none
 proxy.browser-only:                     no
 proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 5122
+size: 5208
 location: src/tests/client/test-client.py:test_003()/19
 cmd: $NMCLI con s con-gsm2
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4979 bytes
+stdout: 5065 bytes
 >>>
 connection.id:                          con-gsm2
 connection.uuid:                        UUID-con-gsm2-REPLACED-REPLACED-REPL
@@ -581,7 +587,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -647,6 +652,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -674,48 +680,50 @@ gsm.device-id:                          --
 gsm.sim-id:                             --
 gsm.sim-operator-id:                    --
 gsm.mtu:                                automatyczne
+gsm.initial-eps-bearer-configure:       nie
+gsm.initial-eps-bearer-apn:             --
 proxy.method:                           none
 proxy.browser-only:                     nie
 proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 493
+size: 495
 location: src/tests/client/test-client.py:test_003()/20
 cmd: $NMCLI -g all con s con-gsm2
 lang: C
 returncode: 0
-stdout: 354 bytes
+stdout: 356 bytes
 >>>
-connection:con-gsm2:UUID-con-gsm2-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-gsm2:UUID-con-gsm2-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::: :0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 serial:5:8:even:1:100
-gsm:no:::<hidden>:0:::<hidden>:0:no::::auto
+gsm:no:::<hidden>:0:::<hidden>:0:no::::auto:no:
 proxy:none:no::
 
 <<<
-size: 503
+size: 505
 location: src/tests/client/test-client.py:test_003()/21
 cmd: $NMCLI -g all con s con-gsm2
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 354 bytes
+stdout: 356 bytes
 >>>
-connection:con-gsm2:UUID-con-gsm2-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-gsm2:UUID-con-gsm2-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::: :0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 serial:5:8:even:1:100
-gsm:no:::<hidden>:0:::<hidden>:0:no::::auto
+gsm:no:::<hidden>:0:::<hidden>:0:no::::auto:no:
 proxy:none:no::
 
 <<<
-size: 5084
+size: 5170
 location: src/tests/client/test-client.py:test_003()/22
 cmd: $NMCLI con s con-gsm3
 lang: C
 returncode: 0
-stdout: 4951 bytes
+stdout: 5037 bytes
 >>>
 connection.id:                          con-gsm3
 connection.uuid:                        UUID-con-gsm3-REPLACED-REPLACED-REPL
@@ -728,7 +736,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -794,6 +801,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -821,18 +829,20 @@ gsm.device-id:                          --
 gsm.sim-id:                             --
 gsm.sim-operator-id:                    --
 gsm.mtu:                                auto
+gsm.initial-eps-bearer-configure:       no
+gsm.initial-eps-bearer-apn:             --
 proxy.method:                           none
 proxy.browser-only:                     no
 proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 5122
+size: 5208
 location: src/tests/client/test-client.py:test_003()/23
 cmd: $NMCLI con s con-gsm3
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4979 bytes
+stdout: 5065 bytes
 >>>
 connection.id:                          con-gsm3
 connection.uuid:                        UUID-con-gsm3-REPLACED-REPLACED-REPL
@@ -845,7 +855,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -911,6 +920,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -938,39 +948,41 @@ gsm.device-id:                          --
 gsm.sim-id:                             --
 gsm.sim-operator-id:                    --
 gsm.mtu:                                automatyczne
+gsm.initial-eps-bearer-configure:       nie
+gsm.initial-eps-bearer-apn:             --
 proxy.method:                           none
 proxy.browser-only:                     nie
 proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 494
+size: 496
 location: src/tests/client/test-client.py:test_003()/24
 cmd: $NMCLI -g all con s con-gsm3
 lang: C
 returncode: 0
-stdout: 355 bytes
+stdout: 357 bytes
 >>>
-connection:con-gsm3:UUID-con-gsm3-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-gsm3:UUID-con-gsm3-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::: :0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 serial:5:8:even:1:100
-gsm:no:::<hidden>:0: ::<hidden>:0:no::::auto
+gsm:no:::<hidden>:0: ::<hidden>:0:no::::auto:no:
 proxy:none:no::
 
 <<<
-size: 504
+size: 506
 location: src/tests/client/test-client.py:test_003()/25
 cmd: $NMCLI -g all con s con-gsm3
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 355 bytes
+stdout: 357 bytes
 >>>
-connection:con-gsm3:UUID-con-gsm3-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-gsm3:UUID-con-gsm3-REPLACED-REPLACED-REPL::gsm::no:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::: :0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 serial:5:8:even:1:100
-gsm:no:::<hidden>:0: ::<hidden>:0:no::::auto
+gsm:no:::<hidden>:0: ::<hidden>:0:no::::auto:no:
 proxy:none:no::
 
 <<<
@@ -1132,7 +1144,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1213,6 +1224,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -1227,12 +1239,12 @@ proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 4958
+size: 4957
 location: src/tests/client/test-client.py:test_003()/38
 cmd: $NMCLI -f ALL con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4808 bytes
+stdout: 4807 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -1245,7 +1257,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1326,6 +1337,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -1378,7 +1390,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1459,6 +1470,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -1486,12 +1498,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 5664
+size: 5663
 location: src/tests/client/test-client.py:test_003()/42
 cmd: $NMCLI con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5521 bytes
+stdout: 5520 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -1504,7 +1516,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1585,6 +1596,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -2116,7 +2128,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -2197,6 +2208,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -2211,12 +2223,12 @@ proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 4958
+size: 4957
 location: src/tests/client/test-client.py:test_003()/63
 cmd: $NMCLI -f ALL con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4808 bytes
+stdout: 4807 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -2229,7 +2241,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -2310,6 +2321,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -2366,7 +2378,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -2447,6 +2458,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -2488,12 +2500,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 6378
+size: 6377
 location: src/tests/client/test-client.py:test_003()/67
 cmd: $NMCLI con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6235 bytes
+stdout: 6234 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -2506,7 +2518,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -2587,6 +2598,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -3124,7 +3136,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -3205,6 +3216,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -3246,12 +3258,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 6382
+size: 6381
 location: src/tests/client/test-client.py:test_003()/83
 cmd: $NMCLI con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6239 bytes
+stdout: 6238 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -3264,7 +3276,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -3345,6 +3356,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -3404,7 +3416,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -3485,6 +3496,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -3512,12 +3524,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 5708
+size: 5707
 location: src/tests/client/test-client.py:test_003()/85
 cmd: $NMCLI c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5525 bytes
+stdout: 5524 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -3530,7 +3542,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -3611,6 +3622,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -3866,7 +3878,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -3947,6 +3958,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -3988,12 +4000,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 6394
+size: 6393
 location: src/tests/client/test-client.py:test_003()/93
 cmd: $NMCLI --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6239 bytes
+stdout: 6238 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -4006,7 +4018,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4087,6 +4098,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4146,7 +4158,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4227,6 +4238,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4254,12 +4266,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 5720
+size: 5719
 location: src/tests/client/test-client.py:test_003()/95
 cmd: $NMCLI --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5525 bytes
+stdout: 5524 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -4272,7 +4284,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4353,6 +4364,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4627,7 +4639,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4711,6 +4722,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4762,12 +4774,12 @@ GENERAL.MASTER-PATH:                    --
 -------------------------------------------------------------------------------
 
 <<<
-size: 7656
+size: 7655
 location: src/tests/client/test-client.py:test_003()/103
 cmd: $NMCLI --pretty con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 7503 bytes
+stdout: 7502 bytes
 >>>
 ===============================================================================
                     Szczegóły profilu połączenia (ethernet)
@@ -4783,7 +4795,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4867,6 +4878,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4939,7 +4951,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -5023,6 +5034,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -5056,12 +5068,12 @@ GENERAL.MASTER-PATH:                    --
 -------------------------------------------------------------------------------
 
 <<<
-size: 6662
+size: 6661
 location: src/tests/client/test-client.py:test_003()/105
 cmd: $NMCLI --pretty c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6469 bytes
+stdout: 6468 bytes
 >>>
 ===============================================================================
                     Szczegóły profilu połączenia (ethernet)
@@ -5077,7 +5089,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -5161,6 +5172,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -5465,7 +5477,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -5549,6 +5560,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -5600,12 +5612,12 @@ GENERAL.MASTER-PATH:                    --
 -------------------------------------------------------------------------------
 
 <<<
-size: 7668
+size: 7667
 location: src/tests/client/test-client.py:test_003()/113
 cmd: $NMCLI --pretty --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 7503 bytes
+stdout: 7502 bytes
 >>>
 ===============================================================================
                     Szczegóły profilu połączenia (ethernet)
@@ -5621,7 +5633,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -5705,6 +5716,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -5777,7 +5789,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -5861,6 +5872,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -5894,12 +5906,12 @@ GENERAL.MASTER-PATH:                    --
 -------------------------------------------------------------------------------
 
 <<<
-size: 6674
+size: 6673
 location: src/tests/client/test-client.py:test_003()/115
 cmd: $NMCLI --pretty --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6469 bytes
+stdout: 6468 bytes
 >>>
 ===============================================================================
                     Szczegóły profilu połączenia (ethernet)
@@ -5915,7 +5927,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -5999,6 +6010,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -6262,12 +6274,12 @@ UUID-con-gsm3-REPLACED-REPLACED-REPL:gsm
 UUID-con-xx1-REPLACED-REPLACED-REPLA:802-3-ethernet
 
 <<<
-size: 3387
+size: 3382
 location: src/tests/client/test-client.py:test_003()/122
 cmd: $NMCLI --terse con s ethernet
 lang: C
 returncode: 0
-stdout: 3245 bytes
+stdout: 3240 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -6280,7 +6292,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -6361,6 +6372,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -6402,12 +6414,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3397
+size: 3392
 location: src/tests/client/test-client.py:test_003()/123
 cmd: $NMCLI --terse con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3245 bytes
+stdout: 3240 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -6420,7 +6432,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -6501,6 +6512,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -6542,12 +6554,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3037
+size: 3032
 location: src/tests/client/test-client.py:test_003()/124
 cmd: $NMCLI --terse c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 2855 bytes
+stdout: 2850 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -6560,7 +6572,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -6641,6 +6652,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -6668,12 +6680,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3047
+size: 3042
 location: src/tests/client/test-client.py:test_003()/125
 cmd: $NMCLI --terse c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2855 bytes
+stdout: 2850 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -6686,7 +6698,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -6767,6 +6778,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -7000,12 +7012,12 @@ UUID-con-gsm3-REPLACED-REPLACED-REPL:gsm
 UUID-con-xx1-REPLACED-REPLACED-REPLA:802-3-ethernet
 
 <<<
-size: 3399
+size: 3394
 location: src/tests/client/test-client.py:test_003()/132
 cmd: $NMCLI --terse --color yes con s ethernet
 lang: C
 returncode: 0
-stdout: 3245 bytes
+stdout: 3240 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -7018,7 +7030,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -7099,6 +7110,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -7140,12 +7152,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3409
+size: 3404
 location: src/tests/client/test-client.py:test_003()/133
 cmd: $NMCLI --terse --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3245 bytes
+stdout: 3240 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -7158,7 +7170,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -7239,6 +7250,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -7280,12 +7292,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3049
+size: 3044
 location: src/tests/client/test-client.py:test_003()/134
 cmd: $NMCLI --terse --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 2855 bytes
+stdout: 2850 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -7298,7 +7310,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -7379,6 +7390,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -7406,12 +7418,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3059
+size: 3054
 location: src/tests/client/test-client.py:test_003()/135
 cmd: $NMCLI --terse --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2855 bytes
+stdout: 2850 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -7424,7 +7436,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -7505,6 +7516,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -7742,15 +7754,15 @@ UUID-con-gsm3-REPLACED-REPLACED-REPL  gsm
 UUID-con-xx1-REPLACED-REPLACED-REPLA  ethernet 
 
 <<<
-size: 4261
+size: 4267
 location: src/tests/client/test-client.py:test_003()/142
 cmd: $NMCLI --mode tabular con s ethernet
 lang: C
 returncode: 0
-stdout: 4112 bytes
+stdout: 4118 bytes
 >>>
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu   s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 802-3-ethernet  --    0      --      no              --           --                  --                         --                     auto  --                --            --            default      --                    -1 (default)             
@@ -7758,8 +7770,8 @@ name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-add
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 proxy  none    no            --       --         
@@ -7773,15 +7785,15 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deac
 
 
 <<<
-size: 4311
+size: 4317
 location: src/tests/client/test-client.py:test_003()/143
 cmd: $NMCLI --mode tabular con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4152 bytes
+stdout: 4158 bytes
 >>>
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu           s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 802-3-ethernet  --    0      --      nie             --           --                  --                         --                     automatyczne  --                --            --            default      --                    -1 (default)             
@@ -7789,8 +7801,8 @@ name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-add
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 proxy  none    nie           --       --         
@@ -7804,15 +7816,15 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deza
 
 
 <<<
-size: 3799
+size: 3805
 location: src/tests/client/test-client.py:test_003()/144
 cmd: $NMCLI --mode tabular c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 3610 bytes
+stdout: 3616 bytes
 >>>
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu   s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 802-3-ethernet  --    0      --      no              --           --                  --                         --                     auto  --                --            --            default      --                    -1 (default)             
@@ -7820,8 +7832,8 @@ name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-add
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 proxy  none    no            --       --         
@@ -7831,15 +7843,15 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deac
 
 
 <<<
-size: 3847
+size: 3853
 location: src/tests/client/test-client.py:test_003()/145
 cmd: $NMCLI --mode tabular c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3648 bytes
+stdout: 3654 bytes
 >>>
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu           s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 802-3-ethernet  --    0      --      nie             --           --                  --                         --                     automatyczne  --                --            --            default      --                    -1 (default)             
@@ -7847,8 +7859,8 @@ name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-add
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 proxy  none    nie           --       --         
@@ -7994,15 +8006,15 @@ UUID-con-gsm3-REPLACED-REPLACED-REPL  gsm
 UUID-con-xx1-REPLACED-REPLACED-REPLA  ethernet 
 
 <<<
-size: 4273
+size: 4279
 location: src/tests/client/test-client.py:test_003()/152
 cmd: $NMCLI --mode tabular --color yes con s ethernet
 lang: C
 returncode: 0
-stdout: 4112 bytes
+stdout: 4118 bytes
 >>>
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu   s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 802-3-ethernet  --    0      --      no              --           --                  --                         --                     auto  --                --            --            default      --                    -1 (default)             
@@ -8010,8 +8022,8 @@ name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-add
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 proxy  none    no            --       --         
@@ -8025,15 +8037,15 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deac
 
 
 <<<
-size: 4323
+size: 4329
 location: src/tests/client/test-client.py:test_003()/153
 cmd: $NMCLI --mode tabular --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4152 bytes
+stdout: 4158 bytes
 >>>
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu           s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 802-3-ethernet  --    0      --      nie             --           --                  --                         --                     automatyczne  --                --            --            default      --                    -1 (default)             
@@ -8041,8 +8053,8 @@ name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-add
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 proxy  none    nie           --       --         
@@ -8056,15 +8068,15 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deza
 
 
 <<<
-size: 3811
+size: 3817
 location: src/tests/client/test-client.py:test_003()/154
 cmd: $NMCLI --mode tabular --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 3610 bytes
+stdout: 3616 bytes
 >>>
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu   s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 802-3-ethernet  --    0      --      no              --           --                  --                         --                     auto  --                --            --            default      --                    -1 (default)             
@@ -8072,8 +8084,8 @@ name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-add
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 proxy  none    no            --       --         
@@ -8083,15 +8095,15 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deac
 
 
 <<<
-size: 3859
+size: 3865
 location: src/tests/client/test-client.py:test_003()/155
 cmd: $NMCLI --mode tabular --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3648 bytes
+stdout: 3654 bytes
 >>>
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu           s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 802-3-ethernet  --    0      --      nie             --           --                  --                         --                     automatyczne  --                --            --            default      --                    -1 (default)             
@@ -8099,8 +8111,8 @@ name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-add
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 proxy  none    nie           --       --         
@@ -8262,19 +8274,19 @@ UUID-con-gsm3-REPLACED-REPLACED-REPL  gsm
 UUID-con-xx1-REPLACED-REPLACED-REPLA  ethernet 
 
 <<<
-size: 6863
+size: 6872
 location: src/tests/client/test-client.py:test_003()/162
 cmd: $NMCLI --mode tabular --pretty con s ethernet
 lang: C
 returncode: 0
-stdout: 6705 bytes
+stdout: 6714 bytes
 >>>
 =========================================
   Connection profile details (ethernet)
 =========================================
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu   s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -8284,9 +8296,9 @@ name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  ro
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 --------------------------------------------------
@@ -8309,19 +8321,19 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deac
 
 
 <<<
-size: 6993
+size: 7002
 location: src/tests/client/test-client.py:test_003()/163
 cmd: $NMCLI --mode tabular --pretty con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6825 bytes
+stdout: 6834 bytes
 >>>
 ===========================================
   Szczegóły profilu połączenia (ethernet)
 ===========================================
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu           s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -8331,9 +8343,9 @@ name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  ro
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 --------------------------------------------------
@@ -8356,19 +8368,19 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deza
 
 
 <<<
-size: 5945
+size: 5954
 location: src/tests/client/test-client.py:test_003()/164
 cmd: $NMCLI --mode tabular --pretty c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 5747 bytes
+stdout: 5756 bytes
 >>>
 =========================================
   Connection profile details (ethernet)
 =========================================
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu   s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -8378,9 +8390,9 @@ name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  ro
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 --------------------------------------------------
@@ -8395,19 +8407,19 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deac
 
 
 <<<
-size: 6047
+size: 6056
 location: src/tests/client/test-client.py:test_003()/165
 cmd: $NMCLI --mode tabular --pretty c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5839 bytes
+stdout: 5848 bytes
 >>>
 ===========================================
   Szczegóły profilu połączenia (ethernet)
 ===========================================
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu           s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -8417,9 +8429,9 @@ name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  ro
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 --------------------------------------------------
@@ -8610,19 +8622,19 @@ UUID-con-gsm3-REPLACED-REPLACED-REPL  gsm
 UUID-con-xx1-REPLACED-REPLACED-REPLA  ethernet 
 
 <<<
-size: 6875
+size: 6884
 location: src/tests/client/test-client.py:test_003()/172
 cmd: $NMCLI --mode tabular --pretty --color yes con s ethernet
 lang: C
 returncode: 0
-stdout: 6705 bytes
+stdout: 6714 bytes
 >>>
 =========================================
   Connection profile details (ethernet)
 =========================================
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu   s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -8632,9 +8644,9 @@ name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  ro
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 --------------------------------------------------
@@ -8657,19 +8669,19 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deac
 
 
 <<<
-size: 7005
+size: 7014
 location: src/tests/client/test-client.py:test_003()/173
 cmd: $NMCLI --mode tabular --pretty --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6825 bytes
+stdout: 6834 bytes
 >>>
 ===========================================
   Szczegóły profilu połączenia (ethernet)
 ===========================================
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu           s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -8679,9 +8691,9 @@ name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  ro
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 --------------------------------------------------
@@ -8704,19 +8716,19 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deza
 
 
 <<<
-size: 5957
+size: 5966
 location: src/tests/client/test-client.py:test_003()/174
 cmd: $NMCLI --mode tabular --pretty --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 5747 bytes
+stdout: 5756 bytes
 >>>
 =========================================
   Connection profile details (ethernet)
 =========================================
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu   s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -8726,9 +8738,9 @@ name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  ro
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 --------------------------------------------------
@@ -8743,19 +8755,19 @@ GENERAL  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  eth0     eth0      deac
 
 
 <<<
-size: 6059
+size: 6068
 location: src/tests/client/test-client.py:test_003()/175
 cmd: $NMCLI --mode tabular --pretty --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5839 bytes
+stdout: 5848 bytes
 >>>
 ===========================================
   Szczegóły profilu połączenia (ethernet)
 ===========================================
-name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id        uuid                                  stable-id  type            interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  ethernet  UUID-ethernet-REPLACED-REPLACED-REPL  --         802-3-ethernet  --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name            port  speed  duplex  auto-negotiate  mac-address  cloned-mac-address  generate-mac-address-mask  mac-address-blacklist  mtu           s390-subchannels  s390-nettype  s390-options  wake-on-lan  wake-on-lan-password  accept-all-mac-addresses 
 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -8765,9 +8777,9 @@ name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  ro
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name   method  browser-only  pac-url  pac-script 
 --------------------------------------------------
@@ -8938,66 +8950,66 @@ UUID-con-gsm3-REPLACED-REPLACED-REPL:gsm
 UUID-con-xx1-REPLACED-REPLACED-REPLA:802-3-ethernet
 
 <<<
-size: 887
+size: 885
 location: src/tests/client/test-client.py:test_003()/182
 cmd: $NMCLI --mode tabular --terse con s ethernet
 lang: C
 returncode: 0
-stdout: 731 bytes
+stdout: 729 bytes
 >>>
-connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 802-3-ethernet::0::no:::::auto::::default::-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 proxy:none:no::
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth1:eth1:activated:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth0:eth0:deactivating:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/1:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 <<<
-size: 897
+size: 895
 location: src/tests/client/test-client.py:test_003()/183
 cmd: $NMCLI --mode tabular --terse con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 731 bytes
+stdout: 729 bytes
 >>>
-connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 802-3-ethernet::0::no:::::auto::::default::-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 proxy:none:no::
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth1:eth1:activated:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth0:eth0:deactivating:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/1:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 <<<
-size: 735
+size: 733
 location: src/tests/client/test-client.py:test_003()/184
 cmd: $NMCLI --mode tabular --terse c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 539 bytes
+stdout: 537 bytes
 >>>
-connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 802-3-ethernet::0::no:::::auto::::default::-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 proxy:none:no::
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth0:eth0:deactivating:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/1:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 <<<
-size: 745
+size: 743
 location: src/tests/client/test-client.py:test_003()/185
 cmd: $NMCLI --mode tabular --terse c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 539 bytes
+stdout: 537 bytes
 >>>
-connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 802-3-ethernet::0::no:::::auto::::default::-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 proxy:none:no::
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth0:eth0:deactivating:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/1:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
@@ -9100,66 +9112,66 @@ UUID-con-gsm3-REPLACED-REPLACED-REPL:gsm
 UUID-con-xx1-REPLACED-REPLACED-REPLA:802-3-ethernet
 
 <<<
-size: 899
+size: 897
 location: src/tests/client/test-client.py:test_003()/192
 cmd: $NMCLI --mode tabular --terse --color yes con s ethernet
 lang: C
 returncode: 0
-stdout: 731 bytes
+stdout: 729 bytes
 >>>
-connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 802-3-ethernet::0::no:::::auto::::default::-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 proxy:none:no::
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth1:eth1:activated:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth0:eth0:deactivating:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/1:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 <<<
-size: 909
+size: 907
 location: src/tests/client/test-client.py:test_003()/193
 cmd: $NMCLI --mode tabular --terse --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 731 bytes
+stdout: 729 bytes
 >>>
-connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 802-3-ethernet::0::no:::::auto::::default::-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 proxy:none:no::
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth1:eth1:activated:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth0:eth0:deactivating:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/1:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 <<<
-size: 747
+size: 745
 location: src/tests/client/test-client.py:test_003()/194
 cmd: $NMCLI --mode tabular --terse --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 539 bytes
+stdout: 537 bytes
 >>>
-connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 802-3-ethernet::0::no:::::auto::::default::-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 proxy:none:no::
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth0:eth0:deactivating:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/1:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
 <<<
-size: 757
+size: 755
 location: src/tests/client/test-client.py:test_003()/195
 cmd: $NMCLI --mode tabular --terse --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 539 bytes
+stdout: 537 bytes
 >>>
-connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL::802-3-ethernet::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 802-3-ethernet::0::no:::::auto::::default::-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 proxy:none:no::
 GENERAL:ethernet:UUID-ethernet-REPLACED-REPLACED-REPL:eth0:eth0:deactivating:no:no::no:/org/freedesktop/NetworkManager/ActiveConnection/1:/org/freedesktop/NetworkManager/Settings/Connection/6::
 
@@ -9488,7 +9500,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -9569,6 +9580,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -9610,12 +9622,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 6400
+size: 6399
 location: src/tests/client/test-client.py:test_003()/203
 cmd: $NMCLI --mode multiline con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6239 bytes
+stdout: 6238 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -9628,7 +9640,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -9709,6 +9720,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -9768,7 +9780,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -9849,6 +9860,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -9876,12 +9888,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 5726
+size: 5725
 location: src/tests/client/test-client.py:test_003()/205
 cmd: $NMCLI --mode multiline c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5525 bytes
+stdout: 5524 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -9894,7 +9906,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -9975,6 +9986,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -10434,7 +10446,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -10515,6 +10526,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -10556,12 +10568,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 6412
+size: 6411
 location: src/tests/client/test-client.py:test_003()/213
 cmd: $NMCLI --mode multiline --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6239 bytes
+stdout: 6238 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -10574,7 +10586,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -10655,6 +10666,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -10714,7 +10726,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -10795,6 +10806,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -10822,12 +10834,12 @@ GENERAL.ZONE:                           --
 GENERAL.MASTER-PATH:                    --
 
 <<<
-size: 5738
+size: 5737
 location: src/tests/client/test-client.py:test_003()/215
 cmd: $NMCLI --mode multiline --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5525 bytes
+stdout: 5524 bytes
 >>>
 connection.id:                          ethernet
 connection.uuid:                        UUID-ethernet-REPLACED-REPLACED-REPL
@@ -10840,7 +10852,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -10921,6 +10932,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -11421,7 +11433,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -11505,6 +11516,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -11556,12 +11568,12 @@ GENERAL.MASTER-PATH:                    --
 -------------------------------------------------------------------------------
 
 <<<
-size: 7673
+size: 7672
 location: src/tests/client/test-client.py:test_003()/223
 cmd: $NMCLI --mode multiline --pretty con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 7503 bytes
+stdout: 7502 bytes
 >>>
 ===============================================================================
                     Szczegóły profilu połączenia (ethernet)
@@ -11577,7 +11589,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -11661,6 +11672,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -11733,7 +11745,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -11817,6 +11828,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -11850,12 +11862,12 @@ GENERAL.MASTER-PATH:                    --
 -------------------------------------------------------------------------------
 
 <<<
-size: 6679
+size: 6678
 location: src/tests/client/test-client.py:test_003()/225
 cmd: $NMCLI --mode multiline --pretty c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6469 bytes
+stdout: 6468 bytes
 >>>
 ===============================================================================
                     Szczegóły profilu połączenia (ethernet)
@@ -11871,7 +11883,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -11955,6 +11966,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -12485,7 +12497,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -12569,6 +12580,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -12620,12 +12632,12 @@ GENERAL.MASTER-PATH:                    --
 -------------------------------------------------------------------------------
 
 <<<
-size: 7685
+size: 7684
 location: src/tests/client/test-client.py:test_003()/233
 cmd: $NMCLI --mode multiline --pretty --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 7503 bytes
+stdout: 7502 bytes
 >>>
 ===============================================================================
                     Szczegóły profilu połączenia (ethernet)
@@ -12641,7 +12653,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -12725,6 +12736,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -12797,7 +12809,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -12881,6 +12892,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -12914,12 +12926,12 @@ GENERAL.MASTER-PATH:                    --
 -------------------------------------------------------------------------------
 
 <<<
-size: 6691
+size: 6690
 location: src/tests/client/test-client.py:test_003()/235
 cmd: $NMCLI --mode multiline --pretty --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6469 bytes
+stdout: 6468 bytes
 >>>
 ===============================================================================
                     Szczegóły profilu połączenia (ethernet)
@@ -12935,7 +12947,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -13019,6 +13030,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -13490,12 +13502,12 @@ UUID:UUID-con-xx1-REPLACED-REPLACED-REPLA
 TYPE:802-3-ethernet
 
 <<<
-size: 3404
+size: 3399
 location: src/tests/client/test-client.py:test_003()/242
 cmd: $NMCLI --mode multiline --terse con s ethernet
 lang: C
 returncode: 0
-stdout: 3245 bytes
+stdout: 3240 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -13508,7 +13520,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -13589,6 +13600,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -13630,12 +13642,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3414
+size: 3409
 location: src/tests/client/test-client.py:test_003()/243
 cmd: $NMCLI --mode multiline --terse con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3245 bytes
+stdout: 3240 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -13648,7 +13660,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -13729,6 +13740,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -13770,12 +13782,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3054
+size: 3049
 location: src/tests/client/test-client.py:test_003()/244
 cmd: $NMCLI --mode multiline --terse c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 2855 bytes
+stdout: 2850 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -13788,7 +13800,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -13869,6 +13880,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -13896,12 +13908,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3064
+size: 3059
 location: src/tests/client/test-client.py:test_003()/245
 cmd: $NMCLI --mode multiline --terse c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2855 bytes
+stdout: 2850 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -13914,7 +13926,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -13995,6 +14006,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -14436,12 +14448,12 @@ UUID:UUID-con-xx1-REPLACED-REPLACED-REPLA
 TYPE:802-3-ethernet
 
 <<<
-size: 3416
+size: 3411
 location: src/tests/client/test-client.py:test_003()/252
 cmd: $NMCLI --mode multiline --terse --color yes con s ethernet
 lang: C
 returncode: 0
-stdout: 3245 bytes
+stdout: 3240 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -14454,7 +14466,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -14535,6 +14546,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -14576,12 +14588,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3426
+size: 3421
 location: src/tests/client/test-client.py:test_003()/253
 cmd: $NMCLI --mode multiline --terse --color yes con s ethernet
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3245 bytes
+stdout: 3240 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -14594,7 +14606,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -14675,6 +14686,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -14716,12 +14728,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3066
+size: 3061
 location: src/tests/client/test-client.py:test_003()/254
 cmd: $NMCLI --mode multiline --terse --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: C
 returncode: 0
-stdout: 2855 bytes
+stdout: 2850 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -14734,7 +14746,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -14815,6 +14826,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -14842,12 +14854,12 @@ GENERAL.ZONE:
 GENERAL.MASTER-PATH:
 
 <<<
-size: 3076
+size: 3071
 location: src/tests/client/test-client.py:test_003()/255
 cmd: $NMCLI --mode multiline --terse --color yes c s /org/freedesktop/NetworkManager/ActiveConnection/1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2855 bytes
+stdout: 2850 bytes
 >>>
 connection.id:ethernet
 connection.uuid:UUID-ethernet-REPLACED-REPLACED-REPL
@@ -14860,7 +14872,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -14941,6 +14952,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
diff --git a/src/tests/client/test-client.check-on-disk/test_004.expected b/src/tests/client/test-client.check-on-disk/test_004.expected
index b69eb1d9..cbc11750 100644
--- a/src/tests/client/test-client.check-on-disk/test_004.expected
+++ b/src/tests/client/test-client.check-on-disk/test_004.expected
@@ -58,12 +58,12 @@ location: src/tests/client/test-client.py:test_004()/7
 cmd: $NMCLI connection mod con-xx1 ipv4.addresses 192.168.77.5/24 ipv4.routes '2.3.4.5/32 192.168.77.1' ipv6.addresses 1:2:3:4::6/64 ipv6.routes 1:2:3:4:5:6::5/128
 lang: C
 returncode: 0
-size: 5164
+size: 5080
 location: src/tests/client/test-client.py:test_004()/8
 cmd: $NMCLI con s con-xx1
 lang: C
 returncode: 0
-stdout: 5033 bytes
+stdout: 4949 bytes
 >>>
 connection.id:                          con-xx1
 connection.uuid:                        UUID-con-xx1-REPLACED-REPLACED-REPLA
@@ -76,7 +76,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -97,8 +96,6 @@ connection.wait-activation-delay:       -1
 802-11-wireless.band:                   a
 802-11-wireless.channel:                0
 802-11-wireless.bssid:                  --
-802-11-wireless.rate:                   0
-802-11-wireless.tx-power:               0
 802-11-wireless.mac-address:            --
 802-11-wireless.cloned-mac-address:     --
 802-11-wireless.generate-mac-address-mask:--
@@ -160,6 +157,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -174,12 +172,12 @@ proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 5200
+size: 5115
 location: src/tests/client/test-client.py:test_004()/9
 cmd: $NMCLI con s con-xx1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5059 bytes
+stdout: 4974 bytes
 >>>
 connection.id:                          con-xx1
 connection.uuid:                        UUID-con-xx1-REPLACED-REPLACED-REPLA
@@ -192,7 +190,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -213,8 +210,6 @@ connection.wait-activation-delay:       -1
 802-11-wireless.band:                   a
 802-11-wireless.channel:                0
 802-11-wireless.bssid:                  --
-802-11-wireless.rate:                   0
-802-11-wireless.tx-power:               0
 802-11-wireless.mac-address:            --
 802-11-wireless.cloned-mac-address:     --
 802-11-wireless.generate-mac-address-mask:--
@@ -276,6 +271,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -344,7 +340,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -410,6 +405,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -430,12 +426,12 @@ proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 4606
+size: 4605
 location: src/tests/client/test-client.py:test_004()/14
 cmd: $NMCLI con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4462 bytes
+stdout: 4461 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -448,7 +444,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -514,6 +509,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -624,7 +620,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -690,6 +685,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -731,12 +727,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5740
+size: 5739
 location: src/tests/client/test-client.py:test_004()/22
 cmd: $NMCLI con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5596 bytes
+stdout: 5595 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -749,7 +745,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -815,6 +810,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -956,7 +952,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1022,6 +1017,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -1063,12 +1059,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5750
+size: 5749
 location: src/tests/client/test-client.py:test_004()/26
 cmd: $NMCLI con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5606 bytes
+stdout: 5605 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -1081,7 +1077,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1147,6 +1142,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -1206,7 +1202,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1272,6 +1267,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -1313,12 +1309,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5750
+size: 5749
 location: src/tests/client/test-client.py:test_004()/28
 cmd: $NMCLI con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5606 bytes
+stdout: 5605 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -1331,7 +1327,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1397,6 +1392,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -1456,7 +1452,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1522,6 +1517,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -1542,12 +1538,12 @@ proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 4613
+size: 4612
 location: src/tests/client/test-client.py:test_004()/30
 cmd: $NMCLI -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4462 bytes
+stdout: 4461 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -1560,7 +1556,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -1626,6 +1621,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4258,7 +4254,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4324,6 +4319,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4365,12 +4361,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5762
+size: 5761
 location: src/tests/client/test-client.py:test_004()/76
 cmd: $NMCLI --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5606 bytes
+stdout: 5605 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -4383,7 +4379,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4449,6 +4444,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4508,7 +4504,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4574,6 +4569,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4615,12 +4611,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5762
+size: 5761
 location: src/tests/client/test-client.py:test_004()/78
 cmd: $NMCLI --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5606 bytes
+stdout: 5605 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -4633,7 +4629,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4699,6 +4694,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4758,7 +4754,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4824,6 +4819,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -4844,12 +4840,12 @@ proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 4625
+size: 4624
 location: src/tests/client/test-client.py:test_004()/80
 cmd: $NMCLI --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4462 bytes
+stdout: 4461 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -4862,7 +4858,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -4928,6 +4923,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -7563,7 +7559,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -7631,6 +7626,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -7680,12 +7676,12 @@ VPN.CFG[3]:                             key3 = val3
 -------------------------------------------------------------------------------
 
 <<<
-size: 6784
+size: 6783
 location: src/tests/client/test-client.py:test_004()/126
 cmd: $NMCLI --pretty con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6630 bytes
+stdout: 6629 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -7701,7 +7697,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -7769,6 +7764,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -7839,7 +7835,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -7907,6 +7902,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -7956,12 +7952,12 @@ VPN.CFG[3]:                             key3 = val3
 -------------------------------------------------------------------------------
 
 <<<
-size: 6784
+size: 6783
 location: src/tests/client/test-client.py:test_004()/128
 cmd: $NMCLI --pretty con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6630 bytes
+stdout: 6629 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -7977,7 +7973,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -8045,6 +8040,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -8115,7 +8111,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -8183,6 +8178,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -8206,12 +8202,12 @@ proxy.pac-script:                       --
 -------------------------------------------------------------------------------
 
 <<<
-size: 5247
+size: 5246
 location: src/tests/client/test-client.py:test_004()/130
 cmd: $NMCLI --pretty -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5086 bytes
+stdout: 5085 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -8227,7 +8223,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -8295,6 +8290,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -11537,7 +11533,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -11605,6 +11600,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -11654,12 +11650,12 @@ VPN.CFG[3]:                             key3 = val3
 -------------------------------------------------------------------------------
 
 <<<
-size: 6796
+size: 6795
 location: src/tests/client/test-client.py:test_004()/176
 cmd: $NMCLI --pretty --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6630 bytes
+stdout: 6629 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -11675,7 +11671,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -11743,6 +11738,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -11813,7 +11809,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -11881,6 +11876,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -11930,12 +11926,12 @@ VPN.CFG[3]:                             key3 = val3
 -------------------------------------------------------------------------------
 
 <<<
-size: 6796
+size: 6795
 location: src/tests/client/test-client.py:test_004()/178
 cmd: $NMCLI --pretty --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6630 bytes
+stdout: 6629 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -11951,7 +11947,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -12019,6 +12014,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -12089,7 +12085,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -12157,6 +12152,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -12180,12 +12176,12 @@ proxy.pac-script:                       --
 -------------------------------------------------------------------------------
 
 <<<
-size: 5259
+size: 5258
 location: src/tests/client/test-client.py:test_004()/180
 cmd: $NMCLI --pretty --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5086 bytes
+stdout: 5085 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -12201,7 +12197,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -12269,6 +12264,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -15490,12 +15486,12 @@ connection.interface-name:              --
 -------------------------------------------------------------------------------
 
 <<<
-size: 2876
+size: 2871
 location: src/tests/client/test-client.py:test_004()/225
 cmd: $NMCLI --terse con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -15508,7 +15504,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -15574,6 +15569,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -15615,12 +15611,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2886
+size: 2881
 location: src/tests/client/test-client.py:test_004()/226
 cmd: $NMCLI --terse con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -15633,7 +15629,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -15699,6 +15694,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -15740,12 +15736,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2876
+size: 2871
 location: src/tests/client/test-client.py:test_004()/227
 cmd: $NMCLI --terse con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -15758,7 +15754,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -15824,6 +15819,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -15865,12 +15861,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2886
+size: 2881
 location: src/tests/client/test-client.py:test_004()/228
 cmd: $NMCLI --terse con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -15883,7 +15879,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -15949,6 +15944,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -15990,12 +15986,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2301
+size: 2296
 location: src/tests/client/test-client.py:test_004()/229
 cmd: $NMCLI --terse -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2151 bytes
+stdout: 2146 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -16008,7 +16004,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -16074,6 +16069,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -16094,12 +16090,12 @@ proxy.pac-url:
 proxy.pac-script:
 
 <<<
-size: 2311
+size: 2306
 location: src/tests/client/test-client.py:test_004()/230
 cmd: $NMCLI --terse -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2151 bytes
+stdout: 2146 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -16112,7 +16108,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -16178,6 +16173,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -18762,12 +18758,12 @@ connection.type:802-11-wireless
 connection.interface-name:
 
 <<<
-size: 2888
+size: 2883
 location: src/tests/client/test-client.py:test_004()/275
 cmd: $NMCLI --terse --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -18780,7 +18776,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -18846,6 +18841,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -18887,12 +18883,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2898
+size: 2893
 location: src/tests/client/test-client.py:test_004()/276
 cmd: $NMCLI --terse --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -18905,7 +18901,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -18971,6 +18966,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -19012,12 +19008,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2888
+size: 2883
 location: src/tests/client/test-client.py:test_004()/277
 cmd: $NMCLI --terse --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -19030,7 +19026,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -19096,6 +19091,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -19137,12 +19133,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2898
+size: 2893
 location: src/tests/client/test-client.py:test_004()/278
 cmd: $NMCLI --terse --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -19155,7 +19151,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -19221,6 +19216,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -19262,12 +19258,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2313
+size: 2308
 location: src/tests/client/test-client.py:test_004()/279
 cmd: $NMCLI --terse --color yes -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2151 bytes
+stdout: 2146 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -19280,7 +19276,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -19346,6 +19341,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -19366,12 +19362,12 @@ proxy.pac-url:
 proxy.pac-script:
 
 <<<
-size: 2323
+size: 2318
 location: src/tests/client/test-client.py:test_004()/280
 cmd: $NMCLI --terse --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2151 bytes
+stdout: 2146 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -19384,7 +19380,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -19450,6 +19445,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -22034,21 +22030,21 @@ connection.type:802-11-wireless
 connection.interface-name:
 
 <<<
-size: 3750
+size: 3756
 location: src/tests/client/test-client.py:test_004()/325
 cmd: $NMCLI --mode tabular con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 3600 bytes
+stdout: 3606 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  no          0       
@@ -22063,21 +22059,21 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 - VPN connected  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 3789
+size: 3795
 location: src/tests/client/test-client.py:test_004()/326
 cmd: $NMCLI --mode tabular con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3629 bytes
+stdout: 3635 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  nie         0       
@@ -22092,21 +22088,21 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 — Połączono z VPN  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 3750
+size: 3756
 location: src/tests/client/test-client.py:test_004()/327
 cmd: $NMCLI --mode tabular con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 3600 bytes
+stdout: 3606 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  no          0       
@@ -22121,21 +22117,21 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 - VPN connected  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 3789
+size: 3795
 location: src/tests/client/test-client.py:test_004()/328
 cmd: $NMCLI --mode tabular con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3629 bytes
+stdout: 3635 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  nie         0       
@@ -22150,21 +22146,21 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 — Połączono z VPN  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 2998
+size: 3004
 location: src/tests/client/test-client.py:test_004()/329
 cmd: $NMCLI --mode tabular -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2841 bytes
+stdout: 2847 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  no          0       
@@ -22174,21 +22170,21 @@ proxy  none    no            --       --
 
 
 <<<
-size: 3026
+size: 3032
 location: src/tests/client/test-client.py:test_004()/330
 cmd: $NMCLI --mode tabular -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2859 bytes
+stdout: 2865 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  nie         0       
@@ -23688,21 +23684,21 @@ interface-name
 
 
 <<<
-size: 3762
+size: 3768
 location: src/tests/client/test-client.py:test_004()/375
 cmd: $NMCLI --mode tabular --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 3600 bytes
+stdout: 3606 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  no          0       
@@ -23717,21 +23713,21 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 - VPN connected  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 3801
+size: 3807
 location: src/tests/client/test-client.py:test_004()/376
 cmd: $NMCLI --mode tabular --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3629 bytes
+stdout: 3635 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  nie         0       
@@ -23746,21 +23742,21 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 — Połączono z VPN  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 3762
+size: 3768
 location: src/tests/client/test-client.py:test_004()/377
 cmd: $NMCLI --mode tabular --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 3600 bytes
+stdout: 3606 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  no          0       
@@ -23775,21 +23771,21 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 - VPN connected  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 3801
+size: 3807
 location: src/tests/client/test-client.py:test_004()/378
 cmd: $NMCLI --mode tabular --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 3629 bytes
+stdout: 3635 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  nie         0       
@@ -23804,21 +23800,21 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 — Połączono z VPN  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 3010
+size: 3016
 location: src/tests/client/test-client.py:test_004()/379
 cmd: $NMCLI --mode tabular --color yes -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2841 bytes
+stdout: 2847 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  no          0       
@@ -23828,21 +23824,21 @@ proxy  none    no            --       --
 
 
 <<<
-size: 3038
+size: 3044
 location: src/tests/client/test-client.py:test_004()/380
 cmd: $NMCLI --mode tabular --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2859 bytes
+stdout: 2865 bytes
 >>>
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 vpn   org.freedesktop.NetworkManager.openvpn  --         key1 = val1, key2 = val2, key3 = val3  <hidden>  nie         0       
@@ -25342,27 +25338,27 @@ interface-name
 
 
 <<<
-size: 5895
+size: 5904
 location: src/tests/client/test-client.py:test_004()/425
 cmd: $NMCLI --mode tabular --pretty con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 5736 bytes
+stdout: 5745 bytes
 >>>
 ==========================================
   Connection profile details (con-vpn-1)
 ==========================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -25384,27 +25380,27 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 - VPN connected  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 5981
+size: 5990
 location: src/tests/client/test-client.py:test_004()/426
 cmd: $NMCLI --mode tabular --pretty con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5812 bytes
+stdout: 5821 bytes
 >>>
 ============================================
   Szczegóły profilu połączenia (con-vpn-1)
 ============================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -25426,27 +25422,27 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 — Połączono z VPN  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 5895
+size: 5904
 location: src/tests/client/test-client.py:test_004()/427
 cmd: $NMCLI --mode tabular --pretty con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 5736 bytes
+stdout: 5745 bytes
 >>>
 ==========================================
   Connection profile details (con-vpn-1)
 ==========================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -25468,27 +25464,27 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 - VPN connected  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 5981
+size: 5990
 location: src/tests/client/test-client.py:test_004()/428
 cmd: $NMCLI --mode tabular --pretty con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5812 bytes
+stdout: 5821 bytes
 >>>
 ============================================
   Szczegóły profilu połączenia (con-vpn-1)
 ============================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -25510,27 +25506,27 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 — Połączono z VPN  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 4557
+size: 4566
 location: src/tests/client/test-client.py:test_004()/429
 cmd: $NMCLI --mode tabular --pretty -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 4391 bytes
+stdout: 4400 bytes
 >>>
 ==========================================
   Connection profile details (con-vpn-1)
 ==========================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -25542,27 +25538,27 @@ proxy  none    no            --       --
 
 
 <<<
-size: 4604
+size: 4613
 location: src/tests/client/test-client.py:test_004()/430
 cmd: $NMCLI --mode tabular --pretty -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4428 bytes
+stdout: 4437 bytes
 >>>
 ============================================
   Szczegóły profilu połączenia (con-vpn-1)
 ============================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -27650,27 +27646,27 @@ interface-name
 
 
 <<<
-size: 5907
+size: 5916
 location: src/tests/client/test-client.py:test_004()/475
 cmd: $NMCLI --mode tabular --pretty --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 5736 bytes
+stdout: 5745 bytes
 >>>
 ==========================================
   Connection profile details (con-vpn-1)
 ==========================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -27692,27 +27688,27 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 - VPN connected  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 5993
+size: 6002
 location: src/tests/client/test-client.py:test_004()/476
 cmd: $NMCLI --mode tabular --pretty --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5812 bytes
+stdout: 5821 bytes
 >>>
 ============================================
   Szczegóły profilu połączenia (con-vpn-1)
 ============================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -27734,27 +27730,27 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 — Połączono z VPN  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 5907
+size: 5916
 location: src/tests/client/test-client.py:test_004()/477
 cmd: $NMCLI --mode tabular --pretty --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 5736 bytes
+stdout: 5745 bytes
 >>>
 ==========================================
   Connection profile details (con-vpn-1)
 ==========================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -27776,27 +27772,27 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 - VPN connected  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 5993
+size: 6002
 location: src/tests/client/test-client.py:test_004()/478
 cmd: $NMCLI --mode tabular --pretty --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5812 bytes
+stdout: 5821 bytes
 >>>
 ============================================
   Szczegóły profilu połączenia (con-vpn-1)
 ============================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -27818,27 +27814,27 @@ NAME  TYPE     USERNAME  GATEWAY  BANNER                            VPN-STATE
 VPN   openvpn  --        --       *** VPN connection con-vpn-1 ***  5 — Połączono z VPN  key1 = val1 | key2 = val2 | key3 = val3 
 
 <<<
-size: 4569
+size: 4578
 location: src/tests/client/test-client.py:test_004()/479
 cmd: $NMCLI --mode tabular --pretty --color yes -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 4391 bytes
+stdout: 4400 bytes
 >>>
 ==========================================
   Connection profile details (con-vpn-1)
 ==========================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          no         --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered  lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              yes          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     unknown  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               --              --         0 (default)   yes                 --             --         0x0 (none)           no             yes       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu   dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        no                  no               no             yes       -1 (default)      -1 (unknown)  default        0 (default)  auto  --            --         --         0 (default)   yes                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -27850,27 +27846,27 @@ proxy  none    no            --       --
 
 
 <<<
-size: 4616
+size: 4625
 location: src/tests/client/test-client.py:test_004()/480
 cmd: $NMCLI --mode tabular --pretty --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4428 bytes
+stdout: 4437 bytes
 >>>
 ============================================
   Szczegóły profilu połączenia (con-vpn-1)
 ============================================
-name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  read-only  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          nie        --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
+name        id         uuid                                  stable-id  type  interface-name  autoconnect  autoconnect-priority  autoconnect-retries  multi-connect  auth-retries  timestamp  permissions  zone  master  slave-type  autoconnect-slaves  secondaries  gateway-ping-timeout  metered   lldp     mdns          llmnr         dns-over-tls  mptcp-flags    wait-device-timeout  wait-activation-delay 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+connection  con-vpn-1  UUID-con-vpn-1-REPLACED-REPLACED-REP  --         vpn   --              tak          0                     -1 (default)         0 (default)    -1            0          --           --    --      --          -1 (default)        --           0                     nieznane  default  -1 (default)  -1 (default)  -1 (default)  0x0 (default)  -1                   -1                    
 
 name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  dhcp-client-id  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-fqdn  dhcp-hostname-flags  never-default  may-fail  required-timeout  dad-timeout   dhcp-vendor-class-identifier  link-local   dhcp-reject-servers  auto-route-ext-gw 
 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 ipv4  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              --              --         0 (default)   tak                 --             --         0x0 (none)           nie            tak       -1 (default)      -1 (default)  --                            0 (default)  --                   -1 (default)      
 
-name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
+name  method  dns  dns-search  dns-options  dns-priority  addresses  gateway  routes  route-metric  route-table  routing-rules  replace-local-rule  ignore-auto-routes  ignore-auto-dns  never-default  may-fail  required-timeout  ip6-privacy   addr-gen-mode  ra-timeout   mtu           dhcp-pd-hint  dhcp-duid  dhcp-iaid  dhcp-timeout  dhcp-send-hostname  dhcp-hostname  dhcp-hostname-flags  auto-route-ext-gw  token 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ipv6  auto    --   --          --           0             --         --       --      -1            0 (unspec)   --             -1 (default)        nie                 nie              nie            tak       -1 (default)      -1 (unknown)  default        0 (default)  automatyczne  --            --         --         0 (default)   tak                 --             0x0 (none)           -1 (default)       --    
 
 name  service-type                            user-name  data                                   secrets   persistent  timeout 
 -------------------------------------------------------------------------------------------------------------------------------
@@ -29958,94 +29954,94 @@ interface-name
 
 
 <<<
-size: 842
+size: 840
 location: src/tests/client/test-client.py:test_004()/525
 cmd: $NMCLI --mode tabular --terse con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 685 bytes
+stdout: 683 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 GENERAL:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP:wlan0:wlan0:activated:no:no::yes:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/3::
 VPN:openvpn:::*** VPN connection con-vpn-1 ***:5 - VPN connected:key1 = val1 | key2 = val2 | key3 = val3
 
 <<<
-size: 852
+size: 850
 location: src/tests/client/test-client.py:test_004()/526
 cmd: $NMCLI --mode tabular --terse con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 685 bytes
+stdout: 683 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 GENERAL:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP:wlan0:wlan0:activated:no:no::yes:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/3::
 VPN:openvpn:::*** VPN connection con-vpn-1 ***:5 - VPN connected:key1 = val1 | key2 = val2 | key3 = val3
 
 <<<
-size: 842
+size: 840
 location: src/tests/client/test-client.py:test_004()/527
 cmd: $NMCLI --mode tabular --terse con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 685 bytes
+stdout: 683 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 GENERAL:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP:wlan0:wlan0:activated:no:no::yes:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/3::
 VPN:openvpn:::*** VPN connection con-vpn-1 ***:5 - VPN connected:key1 = val1 | key2 = val2 | key3 = val3
 
 <<<
-size: 852
+size: 850
 location: src/tests/client/test-client.py:test_004()/528
 cmd: $NMCLI --mode tabular --terse con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 685 bytes
+stdout: 683 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 GENERAL:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP:wlan0:wlan0:activated:no:no::yes:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/3::
 VPN:openvpn:::*** VPN connection con-vpn-1 ***:5 - VPN connected:key1 = val1 | key2 = val2 | key3 = val3
 
 <<<
-size: 549
+size: 547
 location: src/tests/client/test-client.py:test_004()/529
 cmd: $NMCLI --mode tabular --terse -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 385 bytes
+stdout: 383 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 
 <<<
-size: 559
+size: 557
 location: src/tests/client/test-client.py:test_004()/530
 cmd: $NMCLI --mode tabular --terse -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 385 bytes
+stdout: 383 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 
@@ -30896,94 +30892,94 @@ UUID-con-xx1-REPLACED-REPLACED-REPLA
 
 
 <<<
-size: 854
+size: 852
 location: src/tests/client/test-client.py:test_004()/575
 cmd: $NMCLI --mode tabular --terse --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 685 bytes
+stdout: 683 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 GENERAL:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP:wlan0:wlan0:activated:no:no::yes:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/3::
 VPN:openvpn:::*** VPN connection con-vpn-1 ***:5 - VPN connected:key1 = val1 | key2 = val2 | key3 = val3
 
 <<<
-size: 864
+size: 862
 location: src/tests/client/test-client.py:test_004()/576
 cmd: $NMCLI --mode tabular --terse --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 685 bytes
+stdout: 683 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 GENERAL:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP:wlan0:wlan0:activated:no:no::yes:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/3::
 VPN:openvpn:::*** VPN connection con-vpn-1 ***:5 - VPN connected:key1 = val1 | key2 = val2 | key3 = val3
 
 <<<
-size: 854
+size: 852
 location: src/tests/client/test-client.py:test_004()/577
 cmd: $NMCLI --mode tabular --terse --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 685 bytes
+stdout: 683 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 GENERAL:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP:wlan0:wlan0:activated:no:no::yes:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/3::
 VPN:openvpn:::*** VPN connection con-vpn-1 ***:5 - VPN connected:key1 = val1 | key2 = val2 | key3 = val3
 
 <<<
-size: 864
+size: 862
 location: src/tests/client/test-client.py:test_004()/578
 cmd: $NMCLI --mode tabular --terse --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 685 bytes
+stdout: 683 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 GENERAL:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP:wlan0:wlan0:activated:no:no::yes:/org/freedesktop/NetworkManager/ActiveConnection/2:/org/freedesktop/NetworkManager/Settings/Connection/3::
 VPN:openvpn:::*** VPN connection con-vpn-1 ***:5 - VPN connected:key1 = val1 | key2 = val2 | key3 = val3
 
 <<<
-size: 561
+size: 559
 location: src/tests/client/test-client.py:test_004()/579
 cmd: $NMCLI --mode tabular --terse --color yes -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 385 bytes
+stdout: 383 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 
 <<<
-size: 571
+size: 569
 location: src/tests/client/test-client.py:test_004()/580
 cmd: $NMCLI --mode tabular --terse --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 385 bytes
+stdout: 383 bytes
 >>>
-connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:no:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
+connection:con-vpn-1:UUID-con-vpn-1-REPLACED-REPLACED-REP::vpn::yes:0:-1:0:-1:0:::::-1::0:unknown:default:-1:-1:-1:0x0:-1:-1
 ipv4:auto::::0::::-1:0::-1:no:no:::0:yes:::0x0:no:yes:-1:-1::0::-1
-ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto:::0:yes::0x0:-1:
+ipv6:auto::::0::::-1:0::-1:no:no:no:yes:-1:-1:default:0:auto::::0:yes::0x0:-1:
 vpn:org.freedesktop.NetworkManager.openvpn::key1 = val1, key2 = val2, key3 = val3:<hidden>:no:0
 proxy:none:no::
 
@@ -31852,7 +31848,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -31918,6 +31913,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -31959,12 +31955,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5768
+size: 5767
 location: src/tests/client/test-client.py:test_004()/626
 cmd: $NMCLI --mode multiline con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5606 bytes
+stdout: 5605 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -31977,7 +31973,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -32043,6 +32038,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -32102,7 +32098,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -32168,6 +32163,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -32209,12 +32205,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5768
+size: 5767
 location: src/tests/client/test-client.py:test_004()/628
 cmd: $NMCLI --mode multiline con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5606 bytes
+stdout: 5605 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -32227,7 +32223,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -32293,6 +32288,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -32352,7 +32348,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -32418,6 +32413,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -32438,12 +32434,12 @@ proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 4631
+size: 4630
 location: src/tests/client/test-client.py:test_004()/630
 cmd: $NMCLI --mode multiline -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4462 bytes
+stdout: 4461 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -32456,7 +32452,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -32522,6 +32517,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -35654,7 +35650,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -35720,6 +35715,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -35761,12 +35757,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5780
+size: 5779
 location: src/tests/client/test-client.py:test_004()/676
 cmd: $NMCLI --mode multiline --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5606 bytes
+stdout: 5605 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -35779,7 +35775,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -35845,6 +35840,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -35904,7 +35900,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -35970,6 +35965,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -36011,12 +36007,12 @@ VPN.CFG[2]:                             key2 = val2
 VPN.CFG[3]:                             key3 = val3
 
 <<<
-size: 5780
+size: 5779
 location: src/tests/client/test-client.py:test_004()/678
 cmd: $NMCLI --mode multiline --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5606 bytes
+stdout: 5605 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -36029,7 +36025,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -36095,6 +36090,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -36154,7 +36150,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -36220,6 +36215,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -36240,12 +36236,12 @@ proxy.pac-url:                          --
 proxy.pac-script:                       --
 
 <<<
-size: 4643
+size: 4642
 location: src/tests/client/test-client.py:test_004()/680
 cmd: $NMCLI --mode multiline --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 4462 bytes
+stdout: 4461 bytes
 >>>
 connection.id:                          con-vpn-1
 connection.uuid:                        UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -36258,7 +36254,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -36324,6 +36319,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -39459,7 +39455,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -39527,6 +39522,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -39576,12 +39572,12 @@ VPN.CFG[3]:                             key3 = val3
 -------------------------------------------------------------------------------
 
 <<<
-size: 6801
+size: 6800
 location: src/tests/client/test-client.py:test_004()/726
 cmd: $NMCLI --mode multiline --pretty con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6630 bytes
+stdout: 6629 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -39597,7 +39593,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -39665,6 +39660,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -39735,7 +39731,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -39803,6 +39798,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -39852,12 +39848,12 @@ VPN.CFG[3]:                             key3 = val3
 -------------------------------------------------------------------------------
 
 <<<
-size: 6801
+size: 6800
 location: src/tests/client/test-client.py:test_004()/728
 cmd: $NMCLI --mode multiline --pretty con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6630 bytes
+stdout: 6629 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -39873,7 +39869,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -39941,6 +39936,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -40011,7 +40007,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -40079,6 +40074,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -40102,12 +40098,12 @@ proxy.pac-script:                       --
 -------------------------------------------------------------------------------
 
 <<<
-size: 5264
+size: 5263
 location: src/tests/client/test-client.py:test_004()/730
 cmd: $NMCLI --mode multiline --pretty -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5086 bytes
+stdout: 5085 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -40123,7 +40119,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -40191,6 +40186,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -43963,7 +43959,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -44031,6 +44026,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -44080,12 +44076,12 @@ VPN.CFG[3]:                             key3 = val3
 -------------------------------------------------------------------------------
 
 <<<
-size: 6813
+size: 6812
 location: src/tests/client/test-client.py:test_004()/776
 cmd: $NMCLI --mode multiline --pretty --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6630 bytes
+stdout: 6629 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -44101,7 +44097,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -44169,6 +44164,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -44239,7 +44235,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -44307,6 +44302,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -44356,12 +44352,12 @@ VPN.CFG[3]:                             key3 = val3
 -------------------------------------------------------------------------------
 
 <<<
-size: 6813
+size: 6812
 location: src/tests/client/test-client.py:test_004()/778
 cmd: $NMCLI --mode multiline --pretty --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 6630 bytes
+stdout: 6629 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -44377,7 +44373,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -44445,6 +44440,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -44515,7 +44511,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   no
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -44583,6 +44578,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               auto
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -44606,12 +44602,12 @@ proxy.pac-script:                       --
 -------------------------------------------------------------------------------
 
 <<<
-size: 5276
+size: 5275
 location: src/tests/client/test-client.py:test_004()/780
 cmd: $NMCLI --mode multiline --pretty --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 5086 bytes
+stdout: 5085 bytes
 >>>
 ===============================================================================
                    Szczegóły profilu połączenia (con-vpn-1)
@@ -44627,7 +44623,6 @@ connection.autoconnect-retries:         -1 (default)
 connection.multi-connect:               0 (default)
 connection.auth-retries:                -1
 connection.timestamp:                   0
-connection.read-only:                   nie
 connection.permissions:                 --
 connection.zone:                        --
 connection.master:                      --
@@ -44695,6 +44690,7 @@ ipv6.ip6-privacy:                       -1 (unknown)
 ipv6.addr-gen-mode:                     default
 ipv6.ra-timeout:                        0 (default)
 ipv6.mtu:                               automatyczne
+ipv6.dhcp-pd-hint:                      --
 ipv6.dhcp-duid:                         --
 ipv6.dhcp-iaid:                         --
 ipv6.dhcp-timeout:                      0 (default)
@@ -48446,12 +48442,12 @@ connection.interface-name:              --
 -------------------------------------------------------------------------------
 
 <<<
-size: 2893
+size: 2888
 location: src/tests/client/test-client.py:test_004()/825
 cmd: $NMCLI --mode multiline --terse con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -48464,7 +48460,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -48530,6 +48525,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -48571,12 +48567,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2903
+size: 2898
 location: src/tests/client/test-client.py:test_004()/826
 cmd: $NMCLI --mode multiline --terse con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -48589,7 +48585,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -48655,6 +48650,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -48696,12 +48692,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2893
+size: 2888
 location: src/tests/client/test-client.py:test_004()/827
 cmd: $NMCLI --mode multiline --terse con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -48714,7 +48710,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -48780,6 +48775,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -48821,12 +48817,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2903
+size: 2898
 location: src/tests/client/test-client.py:test_004()/828
 cmd: $NMCLI --mode multiline --terse con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -48839,7 +48835,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -48905,6 +48900,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -48946,12 +48942,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2318
+size: 2313
 location: src/tests/client/test-client.py:test_004()/829
 cmd: $NMCLI --mode multiline --terse -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2151 bytes
+stdout: 2146 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -48964,7 +48960,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -49030,6 +49025,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -49050,12 +49046,12 @@ proxy.pac-url:
 proxy.pac-script:
 
 <<<
-size: 2328
+size: 2323
 location: src/tests/client/test-client.py:test_004()/830
 cmd: $NMCLI --mode multiline --terse -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2151 bytes
+stdout: 2146 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -49068,7 +49064,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -49134,6 +49129,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -52248,12 +52244,12 @@ connection.type:802-11-wireless
 connection.interface-name:
 
 <<<
-size: 2905
+size: 2900
 location: src/tests/client/test-client.py:test_004()/875
 cmd: $NMCLI --mode multiline --terse --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -52266,7 +52262,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -52332,6 +52327,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -52373,12 +52369,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2915
+size: 2910
 location: src/tests/client/test-client.py:test_004()/876
 cmd: $NMCLI --mode multiline --terse --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -52391,7 +52387,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -52457,6 +52452,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -52498,12 +52494,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2905
+size: 2900
 location: src/tests/client/test-client.py:test_004()/877
 cmd: $NMCLI --mode multiline --terse --color yes con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -52516,7 +52512,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -52582,6 +52577,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -52623,12 +52619,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2915
+size: 2910
 location: src/tests/client/test-client.py:test_004()/878
 cmd: $NMCLI --mode multiline --terse --color yes con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2733 bytes
+stdout: 2728 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -52641,7 +52637,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -52707,6 +52702,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -52748,12 +52744,12 @@ VPN.CFG[2]:key2 = val2
 VPN.CFG[3]:key3 = val3
 
 <<<
-size: 2330
+size: 2325
 location: src/tests/client/test-client.py:test_004()/879
 cmd: $NMCLI --mode multiline --terse --color yes -f ALL con s con-vpn-1
 lang: C
 returncode: 0
-stdout: 2151 bytes
+stdout: 2146 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -52766,7 +52762,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -52832,6 +52827,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
@@ -52852,12 +52848,12 @@ proxy.pac-url:
 proxy.pac-script:
 
 <<<
-size: 2340
+size: 2335
 location: src/tests/client/test-client.py:test_004()/880
 cmd: $NMCLI --mode multiline --terse --color yes -f ALL con s con-vpn-1
 lang: pl_PL.UTF-8
 returncode: 0
-stdout: 2151 bytes
+stdout: 2146 bytes
 >>>
 connection.id:con-vpn-1
 connection.uuid:UUID-con-vpn-1-REPLACED-REPLACED-REP
@@ -52870,7 +52866,6 @@ connection.autoconnect-retries:-1
 connection.multi-connect:0
 connection.auth-retries:-1
 connection.timestamp:0
-connection.read-only:no
 connection.permissions:
 connection.zone:
 connection.master:
@@ -52936,6 +52931,7 @@ ipv6.ip6-privacy:-1
 ipv6.addr-gen-mode:default
 ipv6.ra-timeout:0
 ipv6.mtu:auto
+ipv6.dhcp-pd-hint:
 ipv6.dhcp-duid:
 ipv6.dhcp-iaid:
 ipv6.dhcp-timeout:0
diff --git a/src/tests/client/test-client.check-on-disk/test_version_warn.expected b/src/tests/client/test-client.check-on-disk/test_version_warn.expected
new file mode 100644
index 00000000..3a22d726
--- /dev/null
+++ b/src/tests/client/test-client.check-on-disk/test_version_warn.expected
@@ -0,0 +1,30 @@
+size: 263
+location: src/tests/client/test-client.py:test_version_warn()/1
+cmd: $NMCLI c
+lang: C
+returncode: 0
+stdout: 1 bytes
+>>>
+
+
+<<<
+stderr: 110 bytes
+>>>
+Warning: nmcli (X.Y.Z) and NetworkManager (A.B.C) versions don't match. Restarting NetworkManager is advised.
+
+<<<
+size: 273
+location: src/tests/client/test-client.py:test_version_warn()/2
+cmd: $NMCLI c
+lang: pl_PL.UTF-8
+returncode: 0
+stdout: 1 bytes
+>>>
+
+
+<<<
+stderr: 110 bytes
+>>>
+Warning: nmcli (X.Y.Z) and NetworkManager (A.B.C) versions don't match. Restarting NetworkManager is advised.
+
+<<<
diff --git a/src/tests/client/test-client.py b/src/tests/client/test-client.py
index e5febe96..baa229cf 100755
--- a/src/tests/client/test-client.py
+++ b/src/tests/client/test-client.py
@@ -68,6 +68,10 @@ ENV_NM_TEST_CLIENT_BUILDDIR = "NM_TEST_CLIENT_BUILDDIR"
 # In particular, you can test also a nmcli binary installed somewhere else.
 ENV_NM_TEST_CLIENT_NMCLI_PATH = "NM_TEST_CLIENT_NMCLI_PATH"
 
+# (optional) Path to nm-cloud-setup. By default, it looks for nm-cloud-setup
+# in build dir.
+ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH = "NM_TEST_CLIENT_CLOUD_SETUP_PATH"
+
 # (optional) The test also compares tranlsated output (l10n). This requires,
 # that you first install the translation in the right place. So, by default,
 # if a test for a translation fails, it will mark the test as skipped, and not
@@ -140,6 +144,13 @@ try:
 except ImportError:
     pexpect = None
 
+try:
+    from http.server import HTTPServer
+    from http.server import BaseHTTPRequestHandler
+    from http.client import HTTPConnection, HTTPResponse
+except ImportError:
+    HTTPServer = None
+
 
 ###############################################################################
 
@@ -165,6 +176,14 @@ class PathConfiguration:
         return v
 
     @staticmethod
+    def test_cloud_meta_mock_path():
+        v = os.path.abspath(
+            PathConfiguration.top_srcdir() + "/tools/test-cloud-meta-mock.py"
+        )
+        assert os.path.exists(v), 'Cannot find cloud metadata mock server at "%s"' % (v)
+        return v
+
+    @staticmethod
     def canonical_script_filename():
         p = "src/tests/client/test-client.py"
         assert (PathConfiguration.top_srcdir() + "/" + p) == os.path.abspath(__file__)
@@ -393,6 +412,34 @@ class Util:
             return None
 
     @staticmethod
+    def file_read_expected(filename):
+        results_expect = []
+        content_expect = Util.file_read(filename)
+        try:
+            base_idx = 0
+            size_prefix = "size: ".encode("utf8")
+            while True:
+                if not content_expect[base_idx : base_idx + 10].startswith(size_prefix):
+                    raise Exception("Unexpected token")
+                j = base_idx + len(size_prefix)
+                i = j
+                if Util.python_has_version(3, 0):
+                    eol = ord("\n")
+                else:
+                    eol = "\n"
+                while content_expect[i] != eol:
+                    i += 1
+                i = i + 1 + int(content_expect[j:i])
+                results_expect.append(content_expect[base_idx:i])
+                if len(content_expect) == i:
+                    break
+                base_idx = i
+        except Exception as e:
+            results_expect = None
+
+        return content_expect, results_expect
+
+    @staticmethod
     def _replace_text_match_join(split_arr, replacement):
         yield split_arr[0]
         for t in split_arr[1:]:
@@ -522,6 +569,158 @@ class Util:
             idx = pexp.expect(pattern_list)
             del pattern_list[idx]
 
+    @staticmethod
+    def skip_without_pexpect(_func=None):
+        if _func is None:
+            if pexpect is None:
+                raise unittest.SkipTest("pexpect not available")
+            return
+
+        def f(*a, **kw):
+            Util.skip_without_pexpect()
+            _func(*a, **kw)
+
+        return f
+
+    @staticmethod
+    def skip_without_dbus_session(_func=None):
+        if _func is None:
+            if not dbus_session_inited:
+                raise unittest.SkipTest(
+                    "Own D-Bus session for testing is not initialized. Do you have dbus-run-session available?"
+                )
+            return
+
+        def f(*a, **kw):
+            Util.skip_without_dbus_session()
+            _func(*a, **kw)
+
+        return f
+
+    @staticmethod
+    def skip_without_NM(_func=None):
+        if _func is None:
+            if NM is None:
+                raise unittest.SkipTest(
+                    "gi.NM is not available. Did you build with introspection?"
+                )
+            return
+
+        def f(*a, **kw):
+            Util.skip_without_NM()
+            _func(*a, **kw)
+
+        return f
+
+    @staticmethod
+    def cmd_create_env(
+        lang="C",
+        calling_num=None,
+        fatal_warnings=_DEFAULT_ARG,
+        extra_env=None,
+    ):
+        if lang == "C":
+            language = ""
+        elif lang == "de_DE.utf8":
+            language = "de"
+        elif lang == "pl_PL.UTF-8":
+            language = "pl"
+        else:
+            raise AssertionError("invalid language %s" % (lang))
+
+        env = {}
+        for k in [
+            "LD_LIBRARY_PATH",
+            "DBUS_SESSION_BUS_ADDRESS",
+            "LIBNM_CLIENT_DEBUG",
+            "LIBNM_CLIENT_DEBUG_FILE",
+        ]:
+            val = os.environ.get(k, None)
+            if val is not None:
+                env[k] = val
+        env["LANG"] = lang
+        env["LANGUAGE"] = language
+        env["LIBNM_USE_SESSION_BUS"] = "1"
+        env["LIBNM_USE_NO_UDEV"] = "1"
+        env["TERM"] = "linux"
+        env["ASAN_OPTIONS"] = conf.get(ENV_NM_TEST_ASAN_OPTIONS)
+        env["LSAN_OPTIONS"] = conf.get(ENV_NM_TEST_LSAN_OPTIONS)
+        env["LBSAN_OPTIONS"] = conf.get(ENV_NM_TEST_UBSAN_OPTIONS)
+        env["XDG_CONFIG_HOME"] = PathConfiguration.srcdir()
+        if calling_num is not None:
+            env["NM_TEST_CALLING_NUM"] = str(calling_num)
+        if fatal_warnings is _DEFAULT_ARG or fatal_warnings:
+            env["G_DEBUG"] = "fatal-warnings"
+        if extra_env is not None:
+            for k, v in extra_env.items():
+                env[k] = v
+        return env
+
+    @staticmethod
+    def cmd_create_argv(cmd_path, args, with_valgrind=None):
+
+        if with_valgrind is None:
+            with_valgrind = conf.get(ENV_NM_TEST_VALGRIND)
+
+        valgrind_log = None
+        cmd = conf.get(cmd_path)
+        if with_valgrind:
+            valgrind_log = tempfile.mkstemp(prefix="nm-test-client-valgrind.")
+            argv = [
+                "valgrind",
+                "--quiet",
+                "--error-exitcode=37",
+                "--leak-check=full",
+                "--gen-suppressions=all",
+                (
+                    "--suppressions="
+                    + PathConfiguration.top_srcdir()
+                    + "/valgrind.suppressions"
+                ),
+                "--num-callers=100",
+                "--log-file=" + valgrind_log[1],
+                cmd,
+            ]
+            libtool = conf.get(ENV_LIBTOOL)
+            if libtool:
+                argv = list(libtool) + ["--mode=execute"] + argv
+        else:
+            argv = [cmd]
+
+        argv.extend(args)
+        return argv, valgrind_log
+
+    @staticmethod
+    def cmd_call_pexpect(cmd_path, args, extra_env):
+        argv, valgrind_log = Util.cmd_create_argv(cmd_path, args)
+        env = Util.cmd_create_env(extra_env=extra_env)
+
+        pexp = pexpect.spawn(argv[0], argv[1:], timeout=10, env=env)
+
+        pexp.str_last_chars = 100000
+
+        typ = collections.namedtuple("CallPexpect", ["pexp", "valgrind_log"])
+        return typ(pexp, valgrind_log)
+
+    @staticmethod
+    def cmd_call_pexpect_nmcli(args, extra_env={}):
+        extra_env = extra_env.copy()
+        extra_env.update({"NO_COLOR": "1"})
+
+        return Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_NMCLI_PATH,
+            args,
+            extra_env,
+        )
+
+    @staticmethod
+    def get_nmcli_version():
+        ver = NM.utils_version()
+        micro = ver & 0xFF
+        minor = (ver >> 8) & 0xFF
+        major = ver >> 16
+        return f"{major}.{minor}.{micro}"
+
 
 ###############################################################################
 
@@ -551,6 +750,20 @@ class Configuration:
                     pass
             if not os.path.exists(v):
                 raise Exception("Missing nmcli binary. Set NM_TEST_CLIENT_NMCLI_PATH?")
+        elif name == ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH:
+            v = os.environ.get(ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, None)
+            if v is None:
+                try:
+                    v = os.path.abspath(
+                        self.get(ENV_NM_TEST_CLIENT_BUILDDIR)
+                        + "/src/nm-cloud-setup/nm-cloud-setup"
+                    )
+                except:
+                    pass
+            if not os.path.exists(v):
+                raise Exception(
+                    "Missing nm-cloud-setup binary. Set NM_TEST_CLIENT_CLOUD_SETUP_PATH?"
+                )
         elif name == ENV_NM_TEST_CLIENT_CHECK_L10N:
             # if we test locales other than 'C', the output of nmcli depends on whether
             # nmcli can load the translations. Unfortunately, I cannot find a way to
@@ -629,11 +842,15 @@ class NMStubServer:
         except:
             return None
 
-    def __init__(self, seed):
+    def __init__(self, seed, version=None):
         service_path = PathConfiguration.test_networkmanager_service_path()
         self._conn = dbus.SessionBus()
         env = os.environ.copy()
         env["NM_TEST_NETWORKMANAGER_SERVICE_SEED"] = seed
+        if version is not None:
+            env["NM_TEST_NETWORKMANAGER_SERVICE_VERSION"] = version
+        else:
+            env["NM_TEST_NETWORKMANAGER_SERVICE_VERSION"] = Util.get_nmcli_version()
         p = subprocess.Popen(
             [sys.executable, service_path], stdin=subprocess.PIPE, env=env
         )
@@ -746,11 +963,27 @@ class NMStubServer:
             )
         return u
 
+    def ReplaceTextConUuid(self, con_name, replacement):
+        return Util.ReplaceTextSimple(
+            Util.memoize_nullary(lambda: self.findConnectionUuid(con_name)),
+            replacement,
+        )
+
     def setProperty(self, path, propname, value, iface_name=None):
         if iface_name is None:
             iface_name = ""
         self.op_SetProperties([(path, [(iface_name, [(propname, value)])])])
 
+    def addAndActivateConnection(
+        self, connection, device, specific_object="", delay=None
+    ):
+        if delay is not None:
+            self.op_SetActiveConnectionStateChangedDelay(device, delay)
+        nm_iface = self._conn_get_main_object(self._conn)
+        self.op_AddAndActivateConnection(
+            connection, device, specific_object, dbus_iface=nm_iface
+        )
+
 
 ###############################################################################
 
@@ -832,21 +1065,25 @@ class AsyncProcess:
 ###############################################################################
 
 
-MAX_JOBS = 15
+class NMTestContext:
+    MAX_JOBS = 15
 
-
-class TestNmcli(unittest.TestCase):
-    def __init__(self, *args, **kwargs):
+    def __init__(self, testMethodName):
+        self.testMethodName = testMethodName
         self._calling_num = {}
         self._skip_test_for_l10n_diff = []
         self._async_jobs = []
-        self._results = []
+        self.ctx_results = []
         self.srv = None
-        unittest.TestCase.__init__(self, *args, **kwargs)
 
-    def srv_start(self):
+    def calling_num(self, calling_fcn):
+        calling_num = self._calling_num.get(calling_fcn, 0) + 1
+        self._calling_num[calling_fcn] = calling_num
+        return calling_num
+
+    def srv_start(self, srv_version=None):
         self.srv_shutdown()
-        self.srv = NMStubServer(self._testMethodName)
+        self.srv = NMStubServer(self.testMethodName, srv_version)
 
     def srv_shutdown(self):
         if self.srv is not None:
@@ -854,72 +1091,165 @@ class TestNmcli(unittest.TestCase):
             self.srv = None
             srv.shutdown()
 
-    def ReplaceTextConUuid(self, con_name, replacement):
-        return Util.ReplaceTextSimple(
-            Util.memoize_nullary(lambda: self.srv.findConnectionUuid(con_name)),
-            replacement,
-        )
+    def async_start(self, wait_all=False):
+
+        while True:
 
-    @staticmethod
-    def _read_expected(filename):
-        results_expect = []
-        content_expect = Util.file_read(filename)
-        try:
-            base_idx = 0
-            size_prefix = "size: ".encode("utf8")
             while True:
-                if not content_expect[base_idx : base_idx + 10].startswith(size_prefix):
-                    raise Exception("Unexpected token")
-                j = base_idx + len(size_prefix)
-                i = j
-                if Util.python_has_version(3, 0):
-                    eol = ord("\n")
-                else:
-                    eol = "\n"
-                while content_expect[i] != eol:
-                    i += 1
-                i = i + 1 + int(content_expect[j:i])
-                results_expect.append(content_expect[base_idx:i])
-                if len(content_expect) == i:
+                for async_job in list(self._async_jobs[0 : self.MAX_JOBS]):
+                    async_job.start()
+                # start up to MAX_JOBS jobs, but poll() and complete those
+                # that are already exited. Retry, until there are no more
+                # jobs to start, or until MAX_JOBS are running.
+                jobs_running = []
+                for async_job in list(self._async_jobs[0 : self.MAX_JOBS]):
+                    if async_job.poll() is not None:
+                        self._async_jobs.remove(async_job)
+                        async_job.wait_and_complete()
+                        continue
+                    jobs_running.append(async_job)
+                if len(jobs_running) >= len(self._async_jobs):
+                    break
+                if len(jobs_running) >= self.MAX_JOBS:
                     break
-                base_idx = i
-        except Exception as e:
-            results_expect = None
 
-        return content_expect, results_expect
+            if not jobs_running:
+                return
+            if not wait_all:
+                return
+
+            # in a loop, indefinitely poll the running jobs until we find one that
+            # completes. Note that poll() itself will raise an exception if a
+            # jobs times out.
+            for async_job in Util.random_job(jobs_running):
+                if async_job.poll(timeout=0.03) is not None:
+                    self._async_jobs.remove(async_job)
+                    async_job.wait_and_complete()
+                    break
 
-    def nmcli_construct_argv(self, args, with_valgrind=None):
+    def async_wait(self):
+        return self.async_start(wait_all=True)
 
-        if with_valgrind is None:
-            with_valgrind = conf.get(ENV_NM_TEST_VALGRIND)
+    def async_append_job(self, async_job):
+        self._async_jobs.append(async_job)
 
-        valgrind_log = None
-        cmd = conf.get(ENV_NM_TEST_CLIENT_NMCLI_PATH)
-        if with_valgrind:
-            valgrind_log = tempfile.mkstemp(prefix="nm-test-client-valgrind.")
-            argv = [
-                "valgrind",
-                "--quiet",
-                "--error-exitcode=37",
-                "--leak-check=full",
-                "--gen-suppressions=all",
-                (
-                    "--suppressions="
-                    + PathConfiguration.top_srcdir()
-                    + "/valgrind.suppressions"
-                ),
-                "--num-callers=100",
-                "--log-file=" + valgrind_log[1],
-                cmd,
-            ]
-            libtool = conf.get(ENV_LIBTOOL)
-            if libtool:
-                argv = list(libtool) + ["--mode=execute"] + argv
+    def run_post(self):
+
+        self.async_wait()
+
+        self.srv_shutdown()
+
+        self._calling_num = None
+
+        results = self.ctx_results
+        self.ctx_results = None
+
+        if len(results) == 0:
+            return
+
+        skip_test_for_l10n_diff = self._skip_test_for_l10n_diff
+        self._skip_test_for_l10n_diff = None
+
+        filename = os.path.abspath(
+            PathConfiguration.srcdir()
+            + "/test-client.check-on-disk/"
+            + self.testMethodName
+            + ".expected"
+        )
+
+        regenerate = conf.get(ENV_NM_TEST_REGENERATE)
+
+        content_expect, results_expect = Util.file_read_expected(filename)
+
+        if results_expect is None:
+            if not regenerate:
+                self.fail(
+                    "Failed to parse expected file '%s'. Let the test write the file by rerunning with NM_TEST_REGENERATE=1"
+                    % (filename)
+                )
         else:
-            argv = [cmd]
+            for i in range(0, min(len(results_expect), len(results))):
+                n = results[i]
+                if results_expect[i] == n["content"]:
+                    continue
+                if regenerate:
+                    continue
+                if n["ignore_l10n_diff"]:
+                    skip_test_for_l10n_diff.append(n["test_name"])
+                    continue
+                print(
+                    "\n\n\nThe file '%s' does not have the expected content:"
+                    % (filename)
+                )
+                print("ACTUAL OUTPUT:\n[[%s]]\n" % (n["content"]))
+                print("EXPECT OUTPUT:\n[[%s]]\n" % (results_expect[i]))
+                print(
+                    "Let the test write the file by rerunning with NM_TEST_REGENERATE=1"
+                )
+                print(
+                    "See howto in %s for details.\n"
+                    % (PathConfiguration.canonical_script_filename())
+                )
+                sys.stdout.flush()
+                self.fail(
+                    "Unexpected output of command, expected %s. Rerun test with NM_TEST_REGENERATE=1 to regenerate files"
+                    % (filename)
+                )
+            if len(results_expect) != len(results):
+                if not regenerate:
+                    print(
+                        "\n\n\nThe number of tests in %s does not match the expected content (%s vs %s):"
+                        % (filename, len(results_expect), len(results))
+                    )
+                    if len(results_expect) < len(results):
+                        print(
+                            "ACTUAL OUTPUT:\n[[%s]]\n"
+                            % (results[len(results_expect)]["content"])
+                        )
+                    else:
+                        print(
+                            "EXPECT OUTPUT:\n[[%s]]\n" % (results_expect[len(results)])
+                        )
+                    print(
+                        "Let the test write the file by rerunning with NM_TEST_REGENERATE=1"
+                    )
+                    print(
+                        "See howto in %s for details.\n"
+                        % (PathConfiguration.canonical_script_filename())
+                    )
+                    sys.stdout.flush()
+                    self.fail(
+                        "Unexpected output of command, expected %s. Rerun test with NM_TEST_REGENERATE=1 to regenerate files"
+                        % (filename)
+                    )
 
-        argv.extend(args)
-        return argv, valgrind_log
+        if regenerate:
+            content_new = b"".join([r["content"] for r in results])
+            if content_new != content_expect:
+                try:
+                    with open(filename, "wb") as content_file:
+                        content_file.write(content_new)
+                except Exception as e:
+                    self.fail("Failure to write '%s': %s" % (filename, e))
+
+        if skip_test_for_l10n_diff:
+            # nmcli loads translations from the installation path. This failure commonly
+            # happens because you did not install the binary in the --prefix, before
+            # running the test. Hence, translations are not available or differ.
+            raise unittest.SkipTest(
+                "Skipped asserting for localized tests %s. Set NM_TEST_CLIENT_CHECK_L10N=1 to force fail."
+                % (",".join(skip_test_for_l10n_diff))
+            )
+
+
+###############################################################################
+
+
+class TestNmcli(unittest.TestCase):
+    def setUp(self):
+        Util.skip_without_dbus_session()
+        Util.skip_without_NM()
+        self.ctx = NMTestContext(self._testMethodName)
 
     def call_nmcli_l(
         self,
@@ -1003,56 +1333,6 @@ class TestNmcli(unittest.TestCase):
                 frame,
             )
 
-    def call_nmcli_pexpect(self, args):
-
-        env = self._env(extra_env={"NO_COLOR": "1"})
-        argv, valgrind_log = self.nmcli_construct_argv(args)
-
-        pexp = pexpect.spawn(argv[0], argv[1:], timeout=10, env=env)
-
-        typ = collections.namedtuple("CallNmcliPexpect", ["pexp", "valgrind_log"])
-        return typ(pexp, valgrind_log)
-
-    def _env(
-        self, lang="C", calling_num=None, fatal_warnings=_DEFAULT_ARG, extra_env=None
-    ):
-        if lang == "C":
-            language = ""
-        elif lang == "de_DE.utf8":
-            language = "de"
-        elif lang == "pl_PL.UTF-8":
-            language = "pl"
-        else:
-            self.fail("invalid language %s" % (lang))
-
-        env = {}
-        for k in [
-            "LD_LIBRARY_PATH",
-            "DBUS_SESSION_BUS_ADDRESS",
-            "LIBNM_CLIENT_DEBUG",
-            "LIBNM_CLIENT_DEBUG_FILE",
-        ]:
-            val = os.environ.get(k, None)
-            if val is not None:
-                env[k] = val
-        env["LANG"] = lang
-        env["LANGUAGE"] = language
-        env["LIBNM_USE_SESSION_BUS"] = "1"
-        env["LIBNM_USE_NO_UDEV"] = "1"
-        env["TERM"] = "linux"
-        env["ASAN_OPTIONS"] = conf.get(ENV_NM_TEST_ASAN_OPTIONS)
-        env["LSAN_OPTIONS"] = conf.get(ENV_NM_TEST_LSAN_OPTIONS)
-        env["LBSAN_OPTIONS"] = conf.get(ENV_NM_TEST_UBSAN_OPTIONS)
-        env["XDG_CONFIG_HOME"] = PathConfiguration.srcdir()
-        if calling_num is not None:
-            env["NM_TEST_CALLING_NUM"] = str(calling_num)
-        if fatal_warnings is _DEFAULT_ARG or fatal_warnings:
-            env["G_DEBUG"] = "fatal-warnings"
-        if extra_env is not None:
-            for k, v in extra_env.items():
-                env[k] = v
-        return env
-
     def _call_nmcli(
         self,
         args,
@@ -1072,11 +1352,10 @@ class TestNmcli(unittest.TestCase):
     ):
 
         if sync_barrier:
-            self.async_wait()
+            self.ctx.async_wait()
 
         calling_fcn = frame.f_code.co_name
-        calling_num = self._calling_num.get(calling_fcn, 0) + 1
-        self._calling_num[calling_fcn] = calling_num
+        calling_num = self.ctx.calling_num(calling_fcn)
 
         test_name = "%s-%03d" % (calling_fcn, calling_num)
 
@@ -1113,7 +1392,9 @@ class TestNmcli(unittest.TestCase):
             self.fail("invalid language %s" % (lang))
 
         # Running under valgrind is not yet supported for those tests.
-        args, valgrind_log = self.nmcli_construct_argv(args, with_valgrind=False)
+        args, valgrind_log = Util.cmd_create_argv(
+            ENV_NM_TEST_CLIENT_NMCLI_PATH, args, with_valgrind=False
+        )
 
         assert valgrind_log is None
 
@@ -1143,8 +1424,8 @@ class TestNmcli(unittest.TestCase):
         if expected_stderr is _DEFAULT_ARG:
             expected_stderr = None
 
-        results_idx = len(self._results)
-        self._results.append(None)
+        results_idx = len(self.ctx.ctx_results)
+        self.ctx.ctx_results.append(None)
 
         def complete_cb(async_job, returncode, stdout, stderr):
 
@@ -1219,212 +1500,47 @@ class TestNmcli(unittest.TestCase):
                     )
                 content = ("size: %s\n" % (len(content))).encode("utf8") + content
 
-                self._results[results_idx] = {
+                self.ctx.ctx_results[results_idx] = {
                     "test_name": test_name,
                     "ignore_l10n_diff": ignore_l10n_diff,
                     "content": content,
                 }
 
-        env = self._env(lang, calling_num, fatal_warnings, extra_env)
+        env = Util.cmd_create_env(lang, calling_num, fatal_warnings, extra_env)
         async_job = AsyncProcess(args=args, env=env, complete_cb=complete_cb)
 
-        self._async_jobs.append(async_job)
-
-        self.async_start(wait_all=sync_barrier)
-
-    def async_start(self, wait_all=False):
-
-        while True:
-
-            while True:
-                for async_job in list(self._async_jobs[0:MAX_JOBS]):
-                    async_job.start()
-                # start up to MAX_JOBS jobs, but poll() and complete those
-                # that are already exited. Retry, until there are no more
-                # jobs to start, or until MAX_JOBS are running.
-                jobs_running = []
-                for async_job in list(self._async_jobs[0:MAX_JOBS]):
-                    if async_job.poll() is not None:
-                        self._async_jobs.remove(async_job)
-                        async_job.wait_and_complete()
-                        continue
-                    jobs_running.append(async_job)
-                if len(jobs_running) >= len(self._async_jobs):
-                    break
-                if len(jobs_running) >= MAX_JOBS:
-                    break
-
-            if not jobs_running:
-                return
-            if not wait_all:
-                return
-
-            # in a loop, indefinitely poll the running jobs until we find one that
-            # completes. Note that poll() itself will raise an exception if a
-            # jobs times out.
-            for async_job in Util.random_job(jobs_running):
-                if async_job.poll(timeout=0.03) is not None:
-                    self._async_jobs.remove(async_job)
-                    async_job.wait_and_complete()
-                    break
-
-    def async_wait(self):
-        return self.async_start(wait_all=True)
-
-    def _nm_test_post(self):
-
-        self.async_wait()
-
-        self.srv_shutdown()
-
-        self._calling_num = None
-
-        results = self._results
-        self._results = None
-
-        if len(results) == 0:
-            return
-
-        skip_test_for_l10n_diff = self._skip_test_for_l10n_diff
-        self._skip_test_for_l10n_diff = None
-
-        test_name = self._testMethodName
-
-        filename = os.path.abspath(
-            PathConfiguration.srcdir()
-            + "/test-client.check-on-disk/"
-            + test_name
-            + ".expected"
-        )
-
-        regenerate = conf.get(ENV_NM_TEST_REGENERATE)
-
-        content_expect, results_expect = self._read_expected(filename)
-
-        if results_expect is None:
-            if not regenerate:
-                self.fail(
-                    "Failed to parse expected file '%s'. Let the test write the file by rerunning with NM_TEST_REGENERATE=1"
-                    % (filename)
-                )
-        else:
-            for i in range(0, min(len(results_expect), len(results))):
-                n = results[i]
-                if results_expect[i] == n["content"]:
-                    continue
-                if regenerate:
-                    continue
-                if n["ignore_l10n_diff"]:
-                    skip_test_for_l10n_diff.append(n["test_name"])
-                    continue
-                print(
-                    "\n\n\nThe file '%s' does not have the expected content:"
-                    % (filename)
-                )
-                print("ACTUAL OUTPUT:\n[[%s]]\n" % (n["content"]))
-                print("EXPECT OUTPUT:\n[[%s]]\n" % (results_expect[i]))
-                print(
-                    "Let the test write the file by rerunning with NM_TEST_REGENERATE=1"
-                )
-                print(
-                    "See howto in %s for details.\n"
-                    % (PathConfiguration.canonical_script_filename())
-                )
-                sys.stdout.flush()
-                self.fail(
-                    "Unexpected output of command, expected %s. Rerun test with NM_TEST_REGENERATE=1 to regenerate files"
-                    % (filename)
-                )
-            if len(results_expect) != len(results):
-                if not regenerate:
-                    print(
-                        "\n\n\nThe number of tests in %s does not match the expected content (%s vs %s):"
-                        % (filename, len(results_expect), len(results))
-                    )
-                    if len(results_expect) < len(results):
-                        print(
-                            "ACTUAL OUTPUT:\n[[%s]]\n"
-                            % (results[len(results_expect)]["content"])
-                        )
-                    else:
-                        print(
-                            "EXPECT OUTPUT:\n[[%s]]\n" % (results_expect[len(results)])
-                        )
-                    print(
-                        "Let the test write the file by rerunning with NM_TEST_REGENERATE=1"
-                    )
-                    print(
-                        "See howto in %s for details.\n"
-                        % (PathConfiguration.canonical_script_filename())
-                    )
-                    sys.stdout.flush()
-                    self.fail(
-                        "Unexpected output of command, expected %s. Rerun test with NM_TEST_REGENERATE=1 to regenerate files"
-                        % (filename)
-                    )
-
-        if regenerate:
-            content_new = b"".join([r["content"] for r in results])
-            if content_new != content_expect:
-                try:
-                    with open(filename, "wb") as content_file:
-                        content_file.write(content_new)
-                except Exception as e:
-                    self.fail("Failure to write '%s': %s" % (filename, e))
+        self.ctx.async_append_job(async_job)
 
-        if skip_test_for_l10n_diff:
-            # nmcli loads translations from the installation path. This failure commonly
-            # happens because you did not install the binary in the --prefix, before
-            # running the test. Hence, translations are not available or differ.
-            self.skipTest(
-                "Skipped asserting for localized tests %s. Set NM_TEST_CLIENT_CHECK_L10N=1 to force fail."
-                % (",".join(skip_test_for_l10n_diff))
-            )
-
-    def skip_without_pexpect(func):
-        def f(self):
-            if pexpect is None:
-                raise unittest.SkipTest("pexpect not available")
-            func(self)
-
-        return f
+        self.ctx.async_start(wait_all=sync_barrier)
 
     def nm_test(func):
         def f(self):
-            self.srv_start()
+            self.ctx.srv_start()
             func(self)
-            self._nm_test_post()
+            self.ctx.run_post()
 
         return f
 
     def nm_test_no_dbus(func):
         def f(self):
             func(self)
-            self._nm_test_post()
+            self.ctx.run_post()
 
         return f
 
-    def setUp(self):
-        if not dbus_session_inited:
-            self.skipTest(
-                "Own D-Bus session for testing is not initialized. Do you have dbus-run-session available?"
-            )
-        if NM is None:
-            self.skipTest("gi.NM is not available. Did you build with introspection?")
-
     def init_001(self):
-        self.srv.op_AddObj("WiredDevice", iface="eth0")
-        self.srv.op_AddObj("WiredDevice", iface="eth1")
-        self.srv.op_AddObj("WifiDevice", iface="wlan0")
-        self.srv.op_AddObj("WifiDevice", iface="wlan1")
+        self.ctx.srv.op_AddObj("WiredDevice", iface="eth0")
+        self.ctx.srv.op_AddObj("WiredDevice", iface="eth1")
+        self.ctx.srv.op_AddObj("WifiDevice", iface="wlan0")
+        self.ctx.srv.op_AddObj("WifiDevice", iface="wlan1")
 
         # add another device with an identical ifname. The D-Bus API itself
         # does not enforce the ifnames are unique.
-        self.srv.op_AddObj("WifiDevice", ident="wlan1/x", iface="wlan1")
+        self.ctx.srv.op_AddObj("WifiDevice", ident="wlan1/x", iface="wlan1")
 
-        self.srv.op_AddObj("WifiAp", device="wlan0", rsnf=0x0)
+        self.ctx.srv.op_AddObj("WifiAp", device="wlan0", rsnf=0x0)
 
-        self.srv.op_AddObj("WifiAp", device="wlan0")
+        self.ctx.srv.op_AddObj("WifiAp", device="wlan0")
 
         NM_AP_FLAGS = getattr(NM, "80211ApSecurityFlags")
         rsnf = 0x0
@@ -1433,11 +1549,11 @@ class TestNmcli(unittest.TestCase):
         rsnf = rsnf | NM_AP_FLAGS.GROUP_TKIP
         rsnf = rsnf | NM_AP_FLAGS.GROUP_CCMP
         rsnf = rsnf | NM_AP_FLAGS.KEY_MGMT_SAE
-        self.srv.op_AddObj("WifiAp", device="wlan0", wpaf=0x0, rsnf=rsnf)
+        self.ctx.srv.op_AddObj("WifiAp", device="wlan0", wpaf=0x0, rsnf=rsnf)
 
-        self.srv.op_AddObj("WifiAp", device="wlan1")
+        self.ctx.srv.op_AddObj("WifiAp", device="wlan1")
 
-        self.srv.addConnection(
+        self.ctx.srv.addConnection(
             {"connection": {"type": "802-3-ethernet", "id": "con-1"}}
         )
 
@@ -1497,7 +1613,9 @@ class TestNmcli(unittest.TestCase):
         replace_uuids = []
 
         replace_uuids.append(
-            self.ReplaceTextConUuid("con-xx1", "UUID-con-xx1-REPLACED-REPLACED-REPLA")
+            self.ctx.srv.ReplaceTextConUuid(
+                "con-xx1", "UUID-con-xx1-REPLACED-REPLACED-REPLA"
+            )
         )
 
         self.call_nmcli(
@@ -1510,7 +1628,7 @@ class TestNmcli(unittest.TestCase):
         for con_name, apn in con_gsm_list:
 
             replace_uuids.append(
-                self.ReplaceTextConUuid(
+                self.ctx.srv.ReplaceTextConUuid(
                     con_name, "UUID-" + con_name + "-REPLACED-REPLACED-REPL"
                 )
             )
@@ -1542,7 +1660,9 @@ class TestNmcli(unittest.TestCase):
             )
 
         replace_uuids.append(
-            self.ReplaceTextConUuid("ethernet", "UUID-ethernet-REPLACED-REPLACED-REPL")
+            self.ctx.srv.ReplaceTextConUuid(
+                "ethernet", "UUID-ethernet-REPLACED-REPLACED-REPL"
+            )
         )
 
         self.call_nmcli(
@@ -1625,9 +1745,9 @@ class TestNmcli(unittest.TestCase):
                 ["-f", "ALL", "-t", "dev", "show", "eth0"], replace_stdout=replace_uuids
             )
 
-        self.async_wait()
+        self.ctx.async_wait()
 
-        self.srv.setProperty(
+        self.ctx.srv.setProperty(
             "/org/freedesktop/NetworkManager/ActiveConnection/1",
             "State",
             dbus.UInt32(NM.ActiveConnectionState.DEACTIVATING),
@@ -1637,8 +1757,8 @@ class TestNmcli(unittest.TestCase):
 
         for i in [0, 1]:
             if i == 1:
-                self.async_wait()
-                self.srv.op_ConnectionSetVisible(False, con_id="ethernet")
+                self.ctx.async_wait()
+                self.ctx.srv.op_ConnectionSetVisible(False, con_id="ethernet")
 
             for mode in Util.iter_nmcli_output_modes():
                 self.call_nmcli_l(
@@ -1671,7 +1791,9 @@ class TestNmcli(unittest.TestCase):
         replace_uuids = []
 
         replace_uuids.append(
-            self.ReplaceTextConUuid("con-xx1", "UUID-con-xx1-REPLACED-REPLACED-REPLA")
+            self.ctx.srv.ReplaceTextConUuid(
+                "con-xx1", "UUID-con-xx1-REPLACED-REPLACED-REPLA"
+            )
         )
 
         self.call_nmcli(
@@ -1714,10 +1836,12 @@ class TestNmcli(unittest.TestCase):
         )
         self.call_nmcli_l(["con", "s", "con-xx1"], replace_stdout=replace_uuids)
 
-        self.async_wait()
+        self.ctx.async_wait()
 
         replace_uuids.append(
-            self.ReplaceTextConUuid("con-vpn-1", "UUID-con-vpn-1-REPLACED-REPLACED-REP")
+            self.ctx.srv.ReplaceTextConUuid(
+                "con-vpn-1", "UUID-con-vpn-1-REPLACED-REPLACED-REP"
+            )
         )
 
         self.call_nmcli(
@@ -1748,16 +1872,16 @@ class TestNmcli(unittest.TestCase):
         self.call_nmcli_l(["con", "s"], replace_stdout=replace_uuids)
         self.call_nmcli_l(["con", "s", "con-vpn-1"], replace_stdout=replace_uuids)
 
-        self.async_wait()
+        self.ctx.async_wait()
 
-        self.srv.setProperty(
+        self.ctx.srv.setProperty(
             "/org/freedesktop/NetworkManager/ActiveConnection/2",
             "VpnState",
             dbus.UInt32(NM.VpnConnectionState.ACTIVATED),
         )
 
         uuids = Util.replace_text_sort_list(
-            [c[1] for c in self.srv.findConnections()], replace_uuids
+            [c[1] for c in self.ctx.srv.findConnections()], replace_uuids
         )
 
         self.call_nmcli_l([], replace_stdout=replace_uuids)
@@ -2025,10 +2149,10 @@ class TestNmcli(unittest.TestCase):
             extra_env=no_dbus_env,
         )
 
-    @skip_without_pexpect
+    @Util.skip_without_pexpect
     @nm_test
     def test_ask_mode(self):
-        nmc = self.call_nmcli_pexpect(["--ask", "c", "add"])
+        nmc = Util.cmd_call_pexpect_nmcli(["--ask", "c", "add"])
         nmc.pexp.expect("Connection type:")
         nmc.pexp.sendline("ethernet")
         nmc.pexp.expect("Interface name:")
@@ -2049,11 +2173,60 @@ class TestNmcli(unittest.TestCase):
         nmc.pexp.expect(pexpect.EOF)
         Util.valgrind_check_log(nmc.valgrind_log, "test_ask_mode")
 
-    @skip_without_pexpect
+    @Util.skip_without_pexpect
+    @nm_test
+    def test_ask_offline(self):
+        # Make sure we're not using D-Bus
+        no_dbus_env = {
+            "DBUS_SYSTEM_BUS_ADDRESS": "very:invalid",
+            "DBUS_SESSION_BUS_ADDRESS": "very:invalid",
+        }
+
+        nmc = Util.cmd_call_pexpect_nmcli(
+            ["--offline", "--ask", "c", "add"], extra_env=no_dbus_env
+        )
+        nmc.pexp.expect("Connection type:")
+        nmc.pexp.sendline("ethernet")
+        nmc.pexp.expect("Interface name:")
+        nmc.pexp.sendline("eth0")
+        nmc.pexp.expect("There are 3 optional settings for Wired Ethernet.")
+        nmc.pexp.expect("Do you want to provide them\? \(yes/no\) \[yes]")
+        nmc.pexp.sendline("no")
+        nmc.pexp.expect("There are 2 optional settings for IPv4 protocol.")
+        nmc.pexp.expect("Do you want to provide them\? \(yes/no\) \[yes]")
+        nmc.pexp.sendline("no")
+        nmc.pexp.expect("There are 2 optional settings for IPv6 protocol.")
+        nmc.pexp.expect("Do you want to provide them\? \(yes/no\) \[yes]")
+        nmc.pexp.sendline("no")
+        nmc.pexp.expect("There are 4 optional settings for Proxy.")
+        nmc.pexp.expect("Do you want to provide them\? \(yes/no\) \[yes]")
+        nmc.pexp.sendline("no")
+        nmc.pexp.expect(
+            "\[connection\]\r\n"
+            + "id=ethernet\r\n"
+            + "uuid=.*\r\n"
+            + "type=ethernet\r\n"
+            + "interface-name=eth0\r\n"
+            + "\r\n"
+            + "\[ethernet\]\r\n"
+            + "\r\n"
+            + "\[ipv4\]\r\n"
+            + "method=auto\r\n"
+            + "\r\n"
+            + "\[ipv6\]\r\n"
+            + "addr-gen-mode=default\r\n"
+            + "method=auto\r\n"
+            + "\r\n"
+            + "\[proxy\]\r\n"
+        )
+        nmc.pexp.expect(pexpect.EOF)
+        Util.valgrind_check_log(nmc.valgrind_log, "test_ask_offline")
+
+    @Util.skip_without_pexpect
     @nm_test
     def test_monitor(self):
         def start_mon(self):
-            nmc = self.call_nmcli_pexpect(["monitor"])
+            nmc = Util.cmd_call_pexpect_nmcli(["monitor"])
             nmc.pexp.expect("NetworkManager is running")
             return nmc
 
@@ -2064,10 +2237,10 @@ class TestNmcli(unittest.TestCase):
 
         nmc = start_mon(self)
 
-        self.srv.op_AddObj("WiredDevice", iface="eth0")
+        self.ctx.srv.op_AddObj("WiredDevice", iface="eth0")
         nmc.pexp.expect("eth0: device created\r\n")
 
-        self.srv.addConnection(
+        self.ctx.srv.addConnection(
             {"connection": {"type": "802-3-ethernet", "id": "con-1"}}
         )
         nmc.pexp.expect("con-1: connection profile created\r\n")
@@ -2075,7 +2248,7 @@ class TestNmcli(unittest.TestCase):
         end_mon(self, nmc)
 
         nmc = start_mon(self)
-        self.srv_shutdown()
+        self.ctx.srv_shutdown()
         Util.pexpect_expect_all(
             nmc.pexp,
             "con-1: connection profile removed",
@@ -2084,6 +2257,425 @@ class TestNmcli(unittest.TestCase):
         nmc.pexp.expect("NetworkManager is stopped")
         end_mon(self, nmc)
 
+    @nm_test_no_dbus  # we need dbus, but we need to pass arguments to srv_start
+    def test_version_warn(self):
+        self.ctx.srv_start(srv_version="A.B.C")
+        self.call_nmcli_l(
+            ["c"],
+            replace_stderr=[
+                Util.ReplaceTextRegex(
+                    r"\(" + Util.get_nmcli_version() + r"\)", "(X.Y.Z)"
+                )
+            ],
+        )
+
+
+###############################################################################
+
+
+class TestNmCloudSetup(unittest.TestCase):
+    def setUp(self):
+        Util.skip_without_dbus_session()
+        Util.skip_without_NM()
+        self.ctx = NMTestContext(self._testMethodName)
+
+    _mac1 = "cc:00:00:00:00:01"
+    _mac2 = "cc:00:00:00:00:02"
+
+    _ip1 = "172.31.26.249"
+    _ip2 = "172.31.176.249"
+
+    def cloud_setup_test(func):
+        """
+        Runs the mock NetworkManager along with a mock cloud metadata service.
+        """
+
+        def f(self):
+            Util.skip_without_pexpect()
+
+            if tuple(sys.version_info[0:2]) < (3, 2):
+                # subprocess.Popen()'s "pass_fd" argument requires at least Python 3.2.
+                raise unittest.SkipTest("This test requires at least Python 3.2")
+
+            s = socket.socket()
+            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+            s.bind(("localhost", 0))
+
+            # The same value as Python's TCPServer uses.
+            # Chosen by summoning the sprit of TCP under influence of
+            # hallucinogenic substances.
+            s.listen(5)
+
+            def pass_socket():
+                os.dup2(s.fileno(), 3)
+
+            service_path = PathConfiguration.test_cloud_meta_mock_path()
+            env = os.environ.copy()
+            env["LISTEN_FDS"] = "1"
+            p = subprocess.Popen(
+                [sys.executable, service_path, "--empty"],
+                stdin=subprocess.PIPE,
+                env=env,
+                pass_fds=(3,),
+                preexec_fn=pass_socket,
+            )
+
+            (hostaddr, port) = s.getsockname()
+            self.md_conn = HTTPConnection(hostaddr, port=port)
+            self.md_url = "http://%s:%d" % (hostaddr, port)
+            s.close()
+
+            error = None
+
+            self.ctx.srv_start()
+            try:
+                func(self)
+            except Exception as e:
+                error = e
+            self.ctx.run_post()
+
+            self.md_conn.close()
+            p.stdin.close()
+            p.terminate()
+            p.wait()
+
+            if error:
+                raise error
+
+        return f
+
+    def _mock_devices(self):
+        # Add a device with an active connection that has IPv4 configured
+        self.ctx.srv.op_AddObj("WiredDevice", iface="eth0", mac="cc:00:00:00:00:01")
+        self.ctx.srv.addAndActivateConnection(
+            {
+                "connection": {"type": "802-3-ethernet", "id": "con-eth0"},
+                "ipv4": {"method": "auto"},
+            },
+            "/org/freedesktop/NetworkManager/Devices/1",
+            delay=0,
+        )
+
+        # The second connection has no IPv4
+        self.ctx.srv.op_AddObj("WiredDevice", iface="eth1", mac="cc:00:00:00:00:02")
+        self.ctx.srv.addAndActivateConnection(
+            {"connection": {"type": "802-3-ethernet", "id": "con-eth1"}},
+            "/org/freedesktop/NetworkManager/Devices/2",
+            "",
+            delay=0,
+        )
+
+    def _mock_path(self, path, body):
+        self.md_conn.request("PUT", path, body=body)
+        self.md_conn.getresponse().read()
+
+    @cloud_setup_test
+    def test_aliyun(self):
+        self._mock_devices()
+
+        _aliyun_meta = "/2016-01-01/meta-data/"
+        _aliyun_macs = _aliyun_meta + "network/interfaces/macs/"
+        self._mock_path(_aliyun_meta, "ami-id\n")
+        self._mock_path(
+            _aliyun_macs, TestNmCloudSetup._mac2 + "\n" + TestNmCloudSetup._mac1
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac2 + "/vpc-cidr-block", "172.31.16.0/20"
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac2 + "/private-ipv4s",
+            TestNmCloudSetup._ip1,
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac2 + "/primary-ip-address",
+            TestNmCloudSetup._ip1,
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac2 + "/netmask", "255.255.255.0"
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac2 + "/gateway", "172.31.26.2"
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac1 + "/vpc-cidr-block", "172.31.166.0/20"
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac1 + "/private-ipv4s",
+            TestNmCloudSetup._ip2,
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac1 + "/primary-ip-address",
+            TestNmCloudSetup._ip2,
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac1 + "/netmask", "255.255.255.0"
+        )
+        self._mock_path(
+            _aliyun_macs + TestNmCloudSetup._mac1 + "/gateway", "172.31.176.2"
+        )
+
+        # Run nm-cloud-setup for the first time
+        nmc = Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
+            [],
+            {
+                "NM_CLOUD_SETUP_ALIYUN_HOST": self.md_url,
+                "NM_CLOUD_SETUP_LOG": "trace",
+                "NM_CLOUD_SETUP_ALIYUN": "yes",
+            },
+        )
+
+        nmc.pexp.expect("provider aliyun detected")
+        nmc.pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
+        nmc.pexp.expect("get-config: start fetching meta data")
+        nmc.pexp.expect("get-config: success")
+        nmc.pexp.expect("meta data received")
+        # One of the devices has no IPv4 configuration to be modified
+        nmc.pexp.expect("device has no suitable applied connection. Skip")
+        # The other one was lacking an address set it up.
+        nmc.pexp.expect("some changes were applied for provider aliyun")
+        nmc.pexp.expect(pexpect.EOF)
+
+        # Run nm-cloud-setup for the second time
+        nmc = Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
+            [],
+            {
+                "NM_CLOUD_SETUP_ALIYUN_HOST": self.md_url,
+                "NM_CLOUD_SETUP_LOG": "trace",
+                "NM_CLOUD_SETUP_ALIYUN": "yes",
+            },
+        )
+
+        nmc.pexp.expect("provider aliyun detected")
+        nmc.pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
+        nmc.pexp.expect("get-config: starting")
+        nmc.pexp.expect("get-config: success")
+        nmc.pexp.expect("meta data received")
+        # No changes this time
+        nmc.pexp.expect('device needs no update to applied connection "con-eth0"')
+        nmc.pexp.expect("no changes were applied for provider aliyun")
+        nmc.pexp.expect(pexpect.EOF)
+
+        Util.valgrind_check_log(nmc.valgrind_log, "test_aliyun")
+
+    @cloud_setup_test
+    def test_azure(self):
+        self._mock_devices()
+
+        _azure_meta = "/metadata/instance"
+        _azure_iface = _azure_meta + "/network/interface/"
+        _azure_query = "?format=text&api-version=2017-04-02"
+        self._mock_path(_azure_meta + _azure_query, "")
+        self._mock_path(_azure_iface + _azure_query, "0\n1\n")
+        self._mock_path(
+            _azure_iface + "0/macAddress" + _azure_query, TestNmCloudSetup._mac1
+        )
+        self._mock_path(
+            _azure_iface + "1/macAddress" + _azure_query, TestNmCloudSetup._mac2
+        )
+        self._mock_path(_azure_iface + "0/ipv4/ipAddress/" + _azure_query, "0\n")
+        self._mock_path(_azure_iface + "1/ipv4/ipAddress/" + _azure_query, "0\n")
+        self._mock_path(
+            _azure_iface + "0/ipv4/ipAddress/0/privateIpAddress" + _azure_query,
+            TestNmCloudSetup._ip1,
+        )
+        self._mock_path(
+            _azure_iface + "1/ipv4/ipAddress/0/privateIpAddress" + _azure_query,
+            TestNmCloudSetup._ip2,
+        )
+        self._mock_path(
+            _azure_iface + "0/ipv4/subnet/0/address/" + _azure_query, "172.31.16.0"
+        )
+        self._mock_path(
+            _azure_iface + "1/ipv4/subnet/0/address/" + _azure_query, "172.31.166.0"
+        )
+        self._mock_path(_azure_iface + "0/ipv4/subnet/0/prefix/" + _azure_query, "20")
+        self._mock_path(_azure_iface + "1/ipv4/subnet/0/prefix/" + _azure_query, "20")
+
+        # Run nm-cloud-setup for the first time
+        nmc = Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
+            [],
+            {
+                "NM_CLOUD_SETUP_AZURE_HOST": self.md_url,
+                "NM_CLOUD_SETUP_LOG": "trace",
+                "NM_CLOUD_SETUP_AZURE": "yes",
+            },
+        )
+
+        nmc.pexp.expect("provider azure detected")
+        nmc.pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
+        nmc.pexp.expect("found azure interfaces: 2")
+        nmc.pexp.expect("interface\[0]: found a matching device with hwaddr")
+        nmc.pexp.expect(
+            "interface\[0]: (received subnet address|received subnet prefix 20)"
+        )
+        nmc.pexp.expect(
+            "interface\[0]: (received subnet address|received subnet prefix 20)"
+        )
+        nmc.pexp.expect("get-config: success")
+        nmc.pexp.expect("meta data received")
+        # One of the devices has no IPv4 configuration to be modified
+        nmc.pexp.expect("device has no suitable applied connection. Skip")
+        # The other one was lacking an address set it up.
+        nmc.pexp.expect("some changes were applied for provider azure")
+        nmc.pexp.expect(pexpect.EOF)
+
+        # Run nm-cloud-setup for the second time
+        nmc = Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
+            [],
+            {
+                "NM_CLOUD_SETUP_AZURE_HOST": self.md_url,
+                "NM_CLOUD_SETUP_LOG": "trace",
+                "NM_CLOUD_SETUP_AZURE": "yes",
+            },
+        )
+
+        nmc.pexp.expect("provider azure detected")
+        nmc.pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
+        nmc.pexp.expect("get-config: starting")
+        nmc.pexp.expect("get-config: success")
+        nmc.pexp.expect("meta data received")
+        # No changes this time
+        nmc.pexp.expect('device needs no update to applied connection "con-eth0"')
+        nmc.pexp.expect("no changes were applied for provider azure")
+        nmc.pexp.expect(pexpect.EOF)
+
+        Util.valgrind_check_log(nmc.valgrind_log, "test_azure")
+
+    @cloud_setup_test
+    def test_ec2(self):
+        self._mock_devices()
+
+        _ec2_macs = "/2018-09-24/meta-data/network/interfaces/macs/"
+        self._mock_path("/latest/meta-data/", "ami-id\n")
+        self._mock_path(
+            _ec2_macs, TestNmCloudSetup._mac2 + "\n" + TestNmCloudSetup._mac1
+        )
+        self._mock_path(
+            _ec2_macs + TestNmCloudSetup._mac2 + "/subnet-ipv4-cidr-block",
+            "172.31.16.0/20",
+        )
+        self._mock_path(
+            _ec2_macs + TestNmCloudSetup._mac2 + "/local-ipv4s", TestNmCloudSetup._ip1
+        )
+        self._mock_path(
+            _ec2_macs + TestNmCloudSetup._mac1 + "/subnet-ipv4-cidr-block",
+            "172.31.166.0/20",
+        )
+        self._mock_path(
+            _ec2_macs + TestNmCloudSetup._mac1 + "/local-ipv4s", TestNmCloudSetup._ip2
+        )
+
+        # Run nm-cloud-setup for the first time
+        nmc = Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
+            [],
+            {
+                "NM_CLOUD_SETUP_EC2_HOST": self.md_url,
+                "NM_CLOUD_SETUP_LOG": "trace",
+                "NM_CLOUD_SETUP_EC2": "yes",
+            },
+        )
+
+        nmc.pexp.expect("provider ec2 detected")
+        nmc.pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
+        nmc.pexp.expect("get-config: starting")
+        nmc.pexp.expect("get-config: success")
+        nmc.pexp.expect("meta data received")
+        # One of the devices has no IPv4 configuration to be modified
+        nmc.pexp.expect("device has no suitable applied connection. Skip")
+        # The other one was lacking an address set it up.
+        nmc.pexp.expect("some changes were applied for provider ec2")
+        nmc.pexp.expect(pexpect.EOF)
+
+        # Run nm-cloud-setup for the second time
+        nmc = Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
+            [],
+            {
+                "NM_CLOUD_SETUP_EC2_HOST": self.md_url,
+                "NM_CLOUD_SETUP_LOG": "trace",
+                "NM_CLOUD_SETUP_EC2": "yes",
+            },
+        )
+
+        nmc.pexp.expect("provider ec2 detected")
+        nmc.pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
+        nmc.pexp.expect("get-config: starting")
+        nmc.pexp.expect("get-config: success")
+        nmc.pexp.expect("meta data received")
+        # No changes this time
+        nmc.pexp.expect('device needs no update to applied connection "con-eth0"')
+        nmc.pexp.expect("no changes were applied for provider ec2")
+        nmc.pexp.expect(pexpect.EOF)
+
+        Util.valgrind_check_log(nmc.valgrind_log, "test_ec2")
+
+    @cloud_setup_test
+    def test_gcp(self):
+        self._mock_devices()
+
+        gcp_meta = "/computeMetadata/v1/instance/"
+        gcp_iface = gcp_meta + "network-interfaces/"
+        self._mock_path(gcp_meta + "id", "")
+        self._mock_path(gcp_iface, "0\n1\n")
+        self._mock_path(gcp_iface + "0/mac", TestNmCloudSetup._mac1)
+        self._mock_path(gcp_iface + "1/mac", TestNmCloudSetup._mac2)
+        self._mock_path(gcp_iface + "0/forwarded-ips/", "0\n")
+        self._mock_path(gcp_iface + "0/forwarded-ips/0", TestNmCloudSetup._ip1)
+        self._mock_path(gcp_iface + "1/forwarded-ips/", "0\n")
+        self._mock_path(gcp_iface + "1/forwarded-ips/0", TestNmCloudSetup._ip2)
+
+        # Run nm-cloud-setup for the first time
+        nmc = Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
+            [],
+            {
+                "NM_CLOUD_SETUP_GCP_HOST": self.md_url,
+                "NM_CLOUD_SETUP_LOG": "trace",
+                "NM_CLOUD_SETUP_GCP": "yes",
+            },
+        )
+
+        nmc.pexp.expect("provider GCP detected")
+        nmc.pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
+        nmc.pexp.expect("found GCP interfaces: 2")
+        nmc.pexp.expect("GCP interface\[0]: found a requested device with hwaddr")
+        nmc.pexp.expect("get-config: success")
+        nmc.pexp.expect("meta data received")
+        # One of the devices has no IPv4 configuration to be modified
+        nmc.pexp.expect("device has no suitable applied connection. Skip")
+        # The other one was lacking an address set it up.
+        nmc.pexp.expect("some changes were applied for provider GCP")
+        nmc.pexp.expect(pexpect.EOF)
+
+        # Run nm-cloud-setup for the second time
+        nmc = Util.cmd_call_pexpect(
+            ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
+            [],
+            {
+                "NM_CLOUD_SETUP_GCP_HOST": self.md_url,
+                "NM_CLOUD_SETUP_LOG": "trace",
+                "NM_CLOUD_SETUP_GCP": "yes",
+            },
+        )
+
+        nmc.pexp.expect("provider GCP detected")
+        nmc.pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
+        nmc.pexp.expect("get-config: starting")
+        nmc.pexp.expect("get-config: success")
+        nmc.pexp.expect("meta data received")
+        # No changes this time
+        nmc.pexp.expect('device needs no update to applied connection "con-eth0"')
+        nmc.pexp.expect("no changes were applied for provider GCP")
+        nmc.pexp.expect(pexpect.EOF)
+
+        Util.valgrind_check_log(nmc.valgrind_log, "test_gcp")
+
 
 ###############################################################################
 
@@ -2107,7 +2699,7 @@ def main():
                     sys.executable,
                     __file__,
                     "--started-with-dbus-session",
-                    *sys.argv[1:]
+                    *sys.argv[1:],
                 )
             except OSError as e:
                 if e.errno != errno.ENOENT:
diff --git a/src/tests/client/test-client.sh b/src/tests/client/test-client.sh
index a636f5fb..dd08e4d8 100755
--- a/src/tests/client/test-client.sh
+++ b/src/tests/client/test-client.sh
@@ -71,7 +71,6 @@ fi
 
 test -d "$BUILDDIR" || die "BUILDDIR \"$BUILDDIR\" does not exist?"
 test -d "$SRCDIR" || die "SRCDIR \"$SRCDIR\" does not exist?"
-test -f "$BUILDDIR/src/nmcli/nmcli" || die "\"$BUILDDIR/src/nmcli/nmcli\" does not exist?"
 
 if test -f "$BUILDDIR/src/libnm-client-impl/.libs/libnm.so" ; then
     LIBDIR="$BUILDDIR/src/libnm-client-impl/.libs"
@@ -84,6 +83,7 @@ fi
 mkdir -p "$BUILDDIR/src/tests/client/" || die "failure to create build output directory \"$BUILDDIR/src/tests/client/\""
 
 export NM_TEST_CLIENT_NMCLI_PATH="$BUILDDIR/src/nmcli/nmcli"
+export NM_TEST_CLIENT_CLOUD_SETUP_PATH="$BUILDDIR/src/nm-cloud-setup/nm-cloud-setup"
 export GI_TYPELIB_PATH="$BUILDDIR/src/libnm-client-impl${GI_TYPELIB_PATH:+:$GI_TYPELIB_PATH}"
 export LD_LIBRARY_PATH="$LIBDIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
 export NM_TEST_CLIENT_BUILDDIR="$BUILDDIR"
@@ -91,7 +91,8 @@ export NM_TEST_CLIENT_BUILDDIR="$BUILDDIR"
 # Run nmcli at least once. With libtool, nmcli is a shell script and with LTO
 # this seems to perform some slow setup during the first run. If we do that
 # during the test, it will timeout and fail.
-"$NM_TEST_CLIENT_NMCLI_PATH" --version &>/dev/null
+"$NM_TEST_CLIENT_NMCLI_PATH" --version &>/dev/null || :
+"$NM_TEST_CLIENT_CLOUD_SETUP_PATH" --invalid &>/dev/null || :
 
 # we first collect all the output in "test-client.log" and print it at once
 # afterwards. The only reason is that when you run with `make -j` that the